/* * Copyright 1999-2006 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* $Id$ */ package org.apache.fop.svg; import org.apache.fop.pdf.PDFConformanceException; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; import org.apache.fop.pdf.PDFGState; import org.apache.fop.pdf.PDFColorSpace; import org.apache.fop.pdf.PDFColor; import org.apache.fop.pdf.PDFState; import org.apache.fop.pdf.PDFNumber; import org.apache.fop.pdf.PDFText; import org.apache.fop.pdf.PDFXObject; import org.apache.fop.pdf.PDFPattern; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFLink; import org.apache.fop.pdf.PDFAnnotList; import org.apache.fop.pdf.BitmapImage; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontSetup; import org.apache.fop.fonts.FontTriplet; import org.apache.fop.fonts.LazyFont; import org.apache.fop.image.JpegImage; import org.apache.fop.fonts.CIDFont; import org.apache.fop.render.pdf.FopPDFImage; import org.apache.batik.ext.awt.g2d.AbstractGraphics2D; import org.apache.batik.ext.awt.g2d.GraphicContext; import org.apache.batik.ext.awt.RadialGradientPaint; import org.apache.batik.ext.awt.LinearGradientPaint; import org.apache.batik.ext.awt.MultipleGradientPaint; import org.apache.batik.ext.awt.RenderingHintsKeyExt; import org.apache.batik.gvt.PatternPaint; import org.apache.batik.gvt.GraphicsNode; import java.text.AttributedCharacterIterator; import java.text.CharacterIterator; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Color; import java.awt.GraphicsConfiguration; /* java.awt.Font is not imported to avoid confusion with org.apache.fop.fonts.Font */ import java.awt.Image; import java.awt.Shape; import java.awt.Stroke; import java.awt.Paint; import java.awt.PaintContext; import java.awt.Rectangle; import java.awt.Dimension; import java.awt.BasicStroke; import java.awt.AlphaComposite; import java.awt.geom.AffineTransform; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.DirectColorModel; import java.awt.image.DataBuffer; import java.awt.image.DataBufferInt; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.StringWriter; import java.io.IOException; import java.io.OutputStream; import java.util.Map; import java.util.List; /** * 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. * * @author Keiron Liddle * @version $Id$ * @see org.apache.batik.ext.awt.g2d.AbstractGraphics2D */ public class PDFGraphics2D extends AbstractGraphics2D { private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); /** The number of decimal places. */ private static final int DEC = 8; /** * 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 current state of the pdf graphics */ protected PDFState graphicsState; /** * The PDF graphics state level that this svg is being drawn into. */ protected int baseLevel = 0; /** * The count of JPEG images added to document so they recieve * unique keys. */ protected int[] jpegCount = {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; resourceContext = page; currentFontName = font; currentFontSize = size; fontInfo = fi; pageRef = pref; graphicsState = new PDFState(); } /** * 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.resourceContext = g.resourceContext; this.currentFontName = g.currentFontName; this.currentFontSize = g.currentFontSize; this.fontInfo = g.fontInfo; this.pageRef = g.pageRef; this.graphicsState = g.graphicsState; this.currentStream = g.currentStream; this.jpegCount = g.jpegCount; 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. */ 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 setPDFState(PDFState state) { graphicsState = state; baseLevel = graphicsState.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 * Grpahics. * @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 Grpahics so far. * @return the StringBuffer containing the PDF markup */ public StringBuffer getBuffer() { return currentStream.getBuffer(); } /** * Set the Grpahics 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"); } /** * 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) { 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, pageRef, pdfdest)); } else { resourceContext.addAnnotation( pdfDoc.getFactory().makeLink(rect, dest, linkType, 0)); } } } /** * Add a JPEG image directly to the PDF document. * This is used by the PDFImageElementBridge to draw a JPEG * directly into the pdf document rather than converting the image into * a bitmap and increasing the size. * * @param jpeg the jpeg 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 addJpegImage(JpegImage jpeg, float x, float y, float width, float height) { preparePainting(); // Need to include hash code as when invoked from FO you // may have several 'independent' PDFGraphics2D so the // count is not enough. String key = "__AddJPEG_" + hashCode() + "_" + jpegCount[0]; jpegCount[0]++; FopPDFImage fopimage = new FopPDFImage(jpeg, key); int xObjectNum = this.pdfDoc.addImage(resourceContext, fopimage).getXNumber(); AffineTransform at = getTransform(); double[] matrix = new double[6]; at.getMatrix(matrix); currentStream.write("q\n"); if (!at.isIdentity()) { concatMatrix(matrix); } Shape imclip = getClip(); writeClip(imclip); currentStream.write("" + width + " 0 0 " + (-height) + " " + x + " " + (y + height) + " cm\n" + "/Im" + xObjectNum + " Do\nQ\n"); 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) */ 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); } /** * Draws as much of the specified image as has already been scaled * to fit inside the specified rectangle. *

* The image is drawn inside the specified rectangle of this * graphics context's coordinate space, and is scaled if * necessary. Transparent pixels do not affect whatever pixels * are already there. *

* This method returns immediately in all cases, even if the * entire image has not yet been scaled, dithered, and converted * for the current output device. * If the current output representation is not yet complete, then * drawImage returns false. As more of * the image becomes available, the process that draws the image notifies * the image observer by calling its imageUpdate method. *

* A scaled version of an image will not necessarily be * available immediately just because an unscaled version of the * image has been constructed for this output device. Each size of * the image may be cached separately and generated from the original * data in a separate image production sequence. * @param img the specified image to be drawn. * @param x the x coordinate. * @param y the y coordinate. * @param width the width of the rectangle. * @param height the height of the rectangle. * @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) */ 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 PDFXObject imageInfo = pdfDoc.getImage("TempImage:" + img.toString()); if (imageInfo == 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); g.clip(new Rectangle(0, 0, buf.getWidth(), buf.getHeight())); g.setComposite(gc.getComposite()); if (!g.drawImage(img, 0, 0, buf.getWidth(), buf.getHeight(), observer)) { return false; } g.dispose(); final byte[] result = new byte[buf.getWidth() * buf.getHeight() * 3 /*for RGB*/]; byte[] mask = new byte[buf.getWidth() * buf.getHeight()]; boolean hasMask = false; //boolean binaryMask = true; Raster raster = buf.getData(); DataBuffer bd = raster.getDataBuffer(); int count = 0; int maskpos = 0; int[] iarray; int i, j, val, alpha; switch (bd.getDataType()) { case DataBuffer.TYPE_INT: int[][] idata = ((DataBufferInt)bd).getBankData(); for (i = 0; i < idata.length; i++) { iarray = idata[i]; for (j = 0; j < iarray.length; j++) { val = iarray[j]; alpha = val >>> 24; mask[maskpos++] = (byte)(alpha & 0xFF); if (alpha != 255) { hasMask = true; } result[count++] = (byte)((val >> 16) & 0xFF); result[count++] = (byte)((val >> 8) & 0xFF); result[count++] = (byte)((val) & 0xFF); } } break; default: // error break; } String ref = null; if (hasMask) { // if the mask is binary then we could convert it into a bitmask BitmapImage fopimg = new BitmapImage("TempImageMask:" + img.toString(), buf.getWidth(), buf.getHeight(), mask, null); fopimg.setColorSpace(new PDFColorSpace(PDFColorSpace.DEVICE_GRAY)); PDFXObject xobj = pdfDoc.addImage(resourceContext, fopimg); ref = xobj.referencePDF(); if (outputStream != null) { try { this.pdfDoc.output(outputStream); } catch (IOException ioe) { // ignore exception, will be thrown again later } } } else { mask = null; } BitmapImage fopimg = new BitmapImage("TempImage:" + img.toString(), buf.getWidth(), buf.getHeight(), result, ref); imageInfo = pdfDoc.addImage(resourceContext, fopimg); //int xObjectNum = imageInfo.getXNumber(); if (outputStream != null) { try { this.pdfDoc.output(outputStream); } catch (IOException ioe) { // ignore exception, will be thrown again later } } } else { resourceContext.getPDFResources().addXObject(imageInfo); } // now do any transformation required and add the actual image // placement instance AffineTransform at = getTransform(); double[] matrix = new double[6]; at.getMatrix(matrix); currentStream.write("q\n"); if (!at.isIdentity()) { concatMatrix(matrix); } Shape imclip = getClip(); writeClip(imclip); currentStream.write("" + width + " 0 0 " + (-height) + " " + x + " " + (y + height) + " cm\n" + "/Im" + imageInfo.getXNumber() + " Do\nQ\n"); 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 */ 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 */ 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 = graphicsState.checkClip(imclip); boolean newTransform = graphicsState.checkTransform(trans) && !trans.isIdentity(); if (newClip || newTransform) { currentStream.write("q\n"); graphicsState.push(); if (newTransform) { concatMatrix(tranvals); } if (newClip) { writeClip(imclip); } } if (c.getAlpha() != 255) { Map vals = new java.util.HashMap(); vals.put(PDFGState.GSTATE_ALPHA_STROKE, new Float(c.getAlpha() / 255f)); PDFGState gstate = pdfDoc.getFactory().makeGState( vals, graphicsState.getGState()); //gstate.setAlpha(c.getAlpha() / 255f, false); resourceContext.addGState(gstate); currentStream.write("/" + gstate.getName() + " gs\n"); } c = getColor(); if (graphicsState.setColor(c)) { applyColor(c, false); } c = getBackground(); if (graphicsState.setBackColor(c)) { applyColor(c, true); } Paint paint = getPaint(); if (graphicsState.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) { currentStream.write("Q\n"); graphicsState.pop(); } return; } } applyStroke(getStroke()); PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM); processPathIterator(iter); doDrawing(false, true, false); if (newClip || newTransform) { currentStream.write("Q\n"); graphicsState.pop(); } } /* // 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; } preparePainting(); PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM); 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(); Color c = col; if (c.getColorSpace().getType() == ColorSpace.TYPE_RGB) { PDFColor currentColour = new PDFColor(c.getRed(), c.getGreen(), c.getBlue()); currentStream.write(currentColour.getColorSpaceOut(fill)); } else if (c.getColorSpace().getType() == ColorSpace.TYPE_CMYK) { if (pdfDoc.getPDFAMode().isPDFA1LevelB()) { //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."); } float[] cComps = c.getColorComponents(new float[3]); double[] cmyk = new double[3]; for (int i = 0; i < 3; i++) { // convert the float elements to doubles for pdf cmyk[i] = cComps[i]; } PDFColor currentColour = new PDFColor(cmyk[0], cmyk[1], cmyk[2], cmyk[3]); currentStream.write(currentColour.getColorSpaceOut(fill)); } else if (c.getColorSpace().getType() == ColorSpace.TYPE_2CLR) { // used for black/magenta float[] cComps = c.getColorComponents(new float[1]); double[] blackMagenta = new double[1]; for (int i = 0; i < 1; i++) { blackMagenta[i] = cComps[i]; } //PDFColor currentColour = new PDFColor(blackMagenta[0], blackMagenta[1]); //currentStream.write(currentColour.getColorSpaceOut(fill)); } else { throw new UnsupportedOperationException( "Color Space not supported by PDFGraphics2D"); } } /** * 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 */ protected boolean applyPaint(Paint paint, boolean fill) { preparePainting(); if (paint instanceof Color) { return true; } 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(graphicsState.getTransform()); 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(new Boolean(true)); theExtend.add(new 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(color1); if (count > 0 && count < cols.length - 1) { theBounds.add(new Double(fractions[count])); } } PDFColorSpace aColorSpace; aColorSpace = new PDFColorSpace(PDFColorSpace.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 repeate // 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(graphicsState.getTransform()); 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(new PDFColor(cc.getRed(), cc.getGreen(), cc.getBlue())); } 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)); } PDFColorSpace colSpace; colSpace = new PDFColorSpace(PDFColorSpace.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 fontInfo = new FontInfo(); FontSetup.setup(fontInfo, null, null); PDFResources res = pdfDoc.getFactory().makeResources(); PDFResourceContext context = new PDFResourceContext(res); PDFGraphics2D pattGraphic = new PDFGraphics2D(textAsShapes, fontInfo, pdfDoc, context, pageRef, "", 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(graphicsState.getTransform()); 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, fontInfo); 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); } if (outputStream != null) { try { this.pdfDoc.output(outputStream); } catch (IOException ioe) { // ignore exception, will be thrown again later } } return true; } protected boolean applyUnknownPaint(Paint paint, Shape shape) { preparePainting(); Shape clip = getClip(); Rectangle2D usrClipBounds, usrBounds; usrBounds = shape.getBounds2D(); 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, devClipBounds, devBounds; AffineTransform at = getTransform(); devShapeBounds = at.createTransformedShape(shape).getBounds(); devClipBounds = at.createTransformedShape(clip).getBounds(); if (!devClipBounds.intersects(devShapeBounds)) { return true; } devBounds = devShapeBounds.intersection(devClipBounds); 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.getImage ("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, y, val, 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 PDFColorSpace(PDFColorSpace.DEVICE_GRAY)); PDFXObject xobj = pdfDoc.addImage(resourceContext, fopimg); maskRef = xobj.referencePDF(); if (outputStream != null) { try { this.pdfDoc.output(outputStream); } catch (IOException ioe) { // ignore exception, will be thrown again later } } } BitmapImage fopimg; fopimg = new BitmapImage("TempImage:" + pctx.toString(), devW, devH, rgb, maskRef); fopimg.setTransparent(new PDFColor(255, 255, 255)); imageInfo = pdfDoc.addImage(resourceContext, fopimg); if (outputStream != null) { try { this.pdfDoc.output(outputStream); } catch (IOException ioe) { // ignore exception, will be thrown again later } } } currentStream.write("q\n"); writeClip(shape); currentStream.write("" + usrW + " 0 0 " + (-usrH) + " " + usrX + " " + (usrY + usrH) + " cm\n" + "/Im" + imageInfo.getXNumber() + " 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++) { if (((int)da[count]) == 0) { // the dasharray units in pdf are (whole) numbers // in user space units, cannot be 0 currentStream.write("1"); } else { currentStream.write("" + ((int)da[count])); } if (count < da.length - 1) { currentStream.write(" "); } } currentStream.write("] "); float offset = bs.getDashPhase(); currentStream.write(((int)offset) + " 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; } 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; } float lw = bs.getLineWidth(); currentStream.write(PDFNumber.doubleOut(lw) + " w\n"); float ml = bs.getMiterLimit(); currentStream.write(PDFNumber.doubleOut(ml) + " M\n"); } } /** * Renders a {@link RenderedImage}, * applying a transform from image * space into user space before drawing. * The transformation from user space into device space is done with * the current Transform in the Graphics2D. * The specified transformation is applied to the image before the * transform attribute in the Graphics2D context is applied. * The rendering attributes applied include the Clip, * Transform, and Composite attributes. Note * that no rendering is done if the specified transform is * noninvertible. * @param img the image to be rendered * @param xform the transformation from image space into user space * @see #transform * @see #setTransform * @see #setComposite * @see #clip * @see #setClip */ public void drawRenderedImage(RenderedImage img, AffineTransform xform) { //NYI } /** * Renders a * {@link RenderableImage}, * applying a transform from image space into user space before drawing. * The transformation from user space into device space is done with * the current Transform in the Graphics2D. * The specified transformation is applied to the image before the * transform attribute in the Graphics2D context is applied. * The rendering attributes applied include the Clip, * Transform, and Composite attributes. Note * that no rendering is done if the specified transform is * noninvertible. *

* Rendering hints set on the Graphics2D object might * be used in rendering the RenderableImage. * If explicit control is required over specific hints recognized by a * specific RenderableImage, or if knowledge of which hints * are used is required, then a RenderedImage should be * obtained directly from the RenderableImage * and rendered using * {@link #drawRenderedImage(RenderedImage, AffineTransform) drawRenderedImage}. * @param img the image to be rendered * @param xform the transformation from image space into user space * @see #transform * @see #setTransform * @see #setComposite * @see #clip * @see #setClip * @see #drawRenderedImage */ public void drawRenderableImage(RenderableImage img, AffineTransform xform) { //NYI } /** * 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 */ 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(); String n = gFont.getFamily(); if (n.equals("sanserif")) { n = "sans-serif"; } float siz = gFont.getSize2D(); String style = gFont.isItalic() ? "italic" : "normal"; int weight = gFont.isBold() ? Font.BOLD : Font.NORMAL; FontTriplet triplet = fontInfo.fontLookup(n, style, weight); fontState = fontInfo.getFontInstance(triplet, (int)(siz * 1000 + 0.5)); } else { fontState = fontInfo.getFontInstance( ovFontState.getFontTriplet(), ovFontState.getFontSize()); ovFontState = null; } String name; float size; name = fontState.getFontName(); size = (float)fontState.getFontSize() / 1000f; if ((!name.equals(this.currentFontName)) || (size != this.currentFontSize)) { this.currentFontName = name; this.currentFontSize = size; currentStream.write("/" + name + " " + size + " Tf\n"); } currentStream.write("q\n"); Color c = getColor(); applyColor(c, true); applyPaint(getPaint(), true); int salpha = c.getAlpha(); if (salpha != 255) { Map vals = new java.util.HashMap(); vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE, new Float(salpha / 255f)); PDFGState gstate = pdfDoc.getFactory().makeGState( vals, graphicsState.getGState()); resourceContext.addGState(gstate); currentStream.write("/" + gstate.getName() + " gs\n"); } Map kerning = null; boolean kerningAvailable = false; kerning = fontState.getKerning(); if (kerning != null && !kerning.isEmpty()) { kerningAvailable = true; } // This assumes that *all* CIDFonts use a /ToUnicode mapping boolean useMultiByte = false; org.apache.fop.fonts.Typeface f = (org.apache.fop.fonts.Typeface)fontInfo.getFonts().get(name); if (f instanceof LazyFont) { if (((LazyFont) f).getRealFont() instanceof CIDFont) { useMultiByte = true; } } else if (f instanceof CIDFont) { useMultiByte = true; } // 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((int)ch)); } else { switch (ch) { case '(': case ')': case '\\': currentStream.write("\\"); break; } currentStream.write(ch); } } else { currentStream.write(PDFText.toUnicodeHex(ch)); } if (kerningAvailable && (i + 1) < l) { addKerning(currentStream, (new Integer((int)ch)), (new Integer((int)fontState.mapChar(s.charAt(i + 1)))), kerning, startText, endText); } } currentStream.write(endText); currentStream.write("] TJ\n"); currentStream.write("ET\n"); currentStream.write("Q\n"); } private void addKerning(StringWriter buf, Integer ch1, Integer ch2, Map kerning, String startText, String endText) { preparePainting(); Map kernPair = (Map)kerning.get(ch1); if (kernPair != null) { Integer width = (Integer)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 */ 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 */ 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 = graphicsState.checkClip(imclip); boolean newTransform = graphicsState.checkTransform(trans) && !trans.isIdentity(); if (newClip || newTransform) { currentStream.write("q\n"); graphicsState.push(); if (newTransform) { concatMatrix(tranvals); } if (newClip) { writeClip(imclip); } } if (c.getAlpha() != 255) { Map vals = new java.util.HashMap(); vals.put(PDFGState.GSTATE_ALPHA_NONSTROKE, new Float(c.getAlpha() / 255f)); PDFGState gstate = pdfDoc.getFactory().makeGState( vals, graphicsState.getGState()); resourceContext.addGState(gstate); currentStream.write("/" + gstate.getName() + " gs\n"); } c = getColor(); if (graphicsState.setColor(c)) { applyColor(c, true); } c = getBackground(); if (graphicsState.setBackColor(c)) { applyColor(c, false); } Paint paint = getPaint(); if (graphicsState.setPaint(paint)) { if (!applyPaint(paint, true)) { // Use the shape to 'clip' the paint contents. applyUnknownPaint(paint, s); if (newClip || newTransform) { currentStream.write("Q\n"); graphicsState.pop(); } return; } } //PathIterator iter = s.getPathIterator(getTransform()); PathIterator iter = s.getPathIterator(IDENTITY_TRANSFORM); processPathIterator(iter); doDrawing(true, false, iter.getWindingRule() == PathIterator.WIND_EVEN_ODD); if (newClip || newTransform) { currentStream.write("Q\n"); graphicsState.pop(); } } /** * Processes a path iterator generating the necessary painting operations. * @param iter PathIterator to process */ public void processPathIterator(PathIterator iter) { while (!iter.isDone()) { double[] vals = new double[6]; int type = iter.currentSegment(vals); switch (type) { case PathIterator.SEG_CUBICTO: 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: currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " l\n"); break; case PathIterator.SEG_MOVETO: currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " m\n"); break; case PathIterator.SEG_QUADTO: currentStream.write(PDFNumber.doubleOut(vals[0], DEC) + " " + PDFNumber.doubleOut(vals[1], DEC) + " " + PDFNumber.doubleOut(vals[2], DEC) + " " + PDFNumber.doubleOut(vals[3], DEC) + " y\n"); 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 */ public GraphicsConfiguration getDeviceConfiguration() { return new PDFGraphicsConfiguration(); } /** * 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() */ 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 */ 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. */ public void copyArea(int x, int y, int width, int height, int dx, int dy) { //NYI } }