From 93a07866a1bb18fb54a5eda61a7f5a996a8522cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Wed, 17 Mar 2021 21:10:23 +0100 Subject: [PATCH] gnujpdf TexturePaint --- lib/gnujpdf.jar | Bin 195422 -> 199353 bytes libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 523361000915e8f73f7b87ba5a5932c0a54efba0..b6bb8011f01e6ab1c59cba6030214f4f4609d702 100644 GIT binary patch delta 33033 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 a_1{l9y}y%Pllov9Y{$85c|WjO`TqeJ_!b`k delta 30340 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 ZK^ErCn2c9D@Yf6f-3efJeZ}+O{{TqDUAX`N diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java index 8c5155a72..4e316e7f2 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java @@ -1 +1 @@ -/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gnu.jpdf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.LinearGradientPaint; import java.awt.MultipleGradientPaint; import java.awt.Paint; import java.awt.Polygon; import java.awt.RadialGradientPaint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.Shape; import java.awt.Stroke; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.awt.print.PageFormat; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.Serializable; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * This class is our implementation of AWT's Graphics class. It provides a Java * standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */ public class PDFGraphics extends Graphics2D implements Serializable { /** * One degree in radians */ private static final double degrees_to_radians = Math.PI / 180.0; private static final int FILL = 1; private static final int STROKE = 2; private static final int CLIP = 3; private static final AffineTransform IDENTITY = new AffineTransform(); private static final Stroke DEF_STROKE = new BasicStroke(); /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.pdf. * The formatting was changed a little bit. * This used to subclass an abstract class in a different package with * the same name (confusing). Now it's one concrete class. * drawImage() was implemented * It is still licensed under the LGPL. */ // Implementation notes: // // Pages 333-335 of the PDF Reference Manual // // Unless absolutely required, use the moveto, lineto and rectangle // operators to perform those actions. // They contain some extra optimizations // which will reduce the output size by up to half in some cases. // // About fill operators: For correct operation, any fill operation should // start with closeBlock(), which will ensure any previous path is completed, // otherwise you may find the fill will include previous items private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH)); private static final DecimalFormat matDf = new DecimalFormat("0", new DecimalFormatSymbols(Locale.ENGLISH)); static { matDf.setMaximumFractionDigits(340); } //JPEXS: cache for already used images private static Map usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * * @see #create */ private boolean child; private Area clip; private AffineTransform clipTransform; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: The last known moveto/lineto x coordinate * * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: The last known moveto/lineto y coordinate * * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: This is written to the stream when the newPath() * is called. np then clears this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties private AffineTransform transform; /** * Part of the optimizer: The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering private String shading = null; private String pattern = null; private Set usedAlphas = new HashSet<>(); private int currentAlpha = 255; private int shadingCount = 0; /** * @see Graphics2D#addRenderingHints(Map) */ @Override public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc, double ayc, double width, double height, double ang1, double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1 % 360.0) * degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width * cos0; y0 = ayc + height * sin0; // NB: !clockwise here as Java Space is inverted to User Space if (!clockwise) { // Quadrant reduction while (ang1 < ang2) { ang2 -= 360.0; } while ((adiff = ang2 - ang1) < -90.0) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while (ang2 < ang1) { ang2 += 360.0; } while ((adiff = ang2 - ang1) > 90.0) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width * sin0; double yt = y0 + trad * height * cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path Important: We * write directly to the stream here, because this method operates in User * space, rather than Java space. * * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w, double h, double x0, double y0, double x3, double y3, double xt, double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx * dx + dy * dy; double w2 = w * w, h2 = h * h; double r2 = w2 + h2; double fw = 0.0, fh = 0.0; if (dist < (r2 * 1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0; } // The path must have a starting point if (first) { moveto(x0, y0); } double x = x0 + ((xt - x0) * fw); double y = y0 + ((yt - y0) * fh); x0 = x3 + ((xt - x3) * fw); y0 = y3 + ((yt - y3) * fh); // Finally the actual curve. curveto(x, y, x0, y0, x3, y3); } /** * This simply draws a White Rectangle to clear the area * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clearRect(int x, int y, int w, int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x, y, w, h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ @Override public void clip(Shape s) { if (s == null) { setClip(null); return; } Area newClip; if (clip == null) { newClip = new Area(s); } else { newClip = (Area) clip.clone(); newClip.intersect(new Area(s)); } setClip(newClip); } /** * This extra method allows PDF users to clip to a Polygon. * *

* In theory you could use setClip(), except that java.awt.Graphics only * supports Rectangle with that method, so we will have an extra method. * * @param p Polygon to clip to */ public void clipPolygon(Polygon p) { closeBlock(); // finish off any existing path polygon(p.xpoints, p.ypoints, p.npoints); closeBlock("W"); // clip to current path clipRectangle = p.getBounds(); } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clipRect(int x, int y, int w, int h) { setClip(x, y, w, h); } /** * All functions should call this to close any existing optimized blocks. */ void closeBlock() { closeBlock("S"); } /** *

* This is used by code that use the path in any way other than Stroke (like * Fill, close path & Stroke etc). Usually this is used internally.

* * @param code PDF operators that will close the path */ void closeBlock(String code) { if (inText) { pw.println("ET Q"); // setOrientation(); // fixes Orientation matrix } if (inStroke) { pw.println(code); } inStroke = inText = false; } /** * This is unsupported - how do you do this with Vector graphics? * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param dx coordinate * @param dy coordinate */ @Override public void copyArea(int x, int y, int w, int h, int dx, int dy) { // Hmm... Probably need to keep track of everything // that has been drawn so far to get the contents of an area } //============ Line operations ======================= /** *

* This returns a child instance of this Graphics object. As with AWT, the * affects of using the parent instance while the child exists, is not * determined.

* *

* Once complete, the child should be released with it's dispose() method * which will restore the graphics state to it's parent.

* * @return Graphics object to render onto the page */ @Override public Graphics create() { closeBlock(); PDFGraphics g = createGraphic(page, pw); // The new instance inherits a few items g.clipRectangle = new Rectangle(clipRectangle); return (Graphics) g; } // end create() /** * This method creates a new instance of the class based on the page and a * print writer. * * @param page the page to attach to * @param pw the PrintWriter to attach to. */ protected PDFGraphics createGraphic(PDFPage page, RawPrintWriter pw) { PDFGraphics g = new PDFGraphics(); g.init(page, pw); return g; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using the current point and (x1,y1) as the * Bezier control points. *

* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(double x1, double y1, double x2, double y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "v"); lx = (float) x2; ly = (float) y2; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier * control points. *

* The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(double x1, double y1, double x2, double y2, double x3, double y3) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c"); lx = (float) x3; ly = (float) y3; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using the current point and (x1,y1) as the * Bezier control points. *

* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(int x1, int y1, int x2, int y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "v"); lx = x2; ly = y2; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier * control points. *

* The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(int x1, int y1, int x2, int y2, int x3, int y3) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c"); lx = x3; ly = y3; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using (x1,y1) and the end point as the * Bezier control points. *

* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(double x1, double y1, double x2, double y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "y"); lx = (float) x2; ly = (float) y2; } // Arcs are horrible and complex. They are at the end of the // file, because they are the largest. This is because, unlike // Postscript, PDF doesn't have any arc operators, so we must // implement them by converting into one or more Bezier curves // (which is how Postscript does them internally). /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using (x1,y1) and the end point as the * Bezier control points. *

* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(int x1, int y1, int x2, int y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "y"); lx = x2; ly = y2; } /** * Converts the Java space dimension into pdf. * * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(double w, double h) { return "" + df.format(w) + " " + df.format(h) + " "; } /** * Converts the Java space dimension into pdf. * * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(int w, int h) { return cwh((double) w, (double) h); } /** * Converts the Java space coordinates into pdf. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(double x, double y) { return "" + df.format(x) + " " + df.format(y) + " "; } /** * Converts the Java space coordinates into pdf. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(int x, int y) { return cxy((double) x, (double) y); } /** *

* This releases any resources used by this Graphics object. You must use * this method once finished with it. Leaving it open will leave the PDF * stream in an inconsistent state, and will produce errors.

* *

* If this was created with Graphics.create() then the parent instance can * be used again. If not, then this closes the graphics operations for this * page when used with PDFJob.

* *

* When using PDFPage, you can create another fresh Graphics instance, which * will draw over this one.

* */ @Override public void dispose() { closeBlock(); if (clip != null) { restoreState(); } if (child) { pw.println("Q"); // restore graphics context } else { pw.close(); // close the stream if were the parent } } // ********************************************* // **** Implementation of java.awt.Graphics **** // ********************************************* //============ Rectangle operations ======================= /** * @see Graphics2D#draw(Shape) */ @Override public void draw(Shape s) { followPath(s, STROKE); } /** *

* Not implemented

* *

* Draws a 3-D highlighted outline of the specified rectangle. The edges of * the rectangle are highlighted so that they appear to be beveled and lit * from the upper left corner. The colors used for the highlighting effect * are determined based on the current color. The resulting rectangle covers * an area that is width + 1 pixels wide by height + 1 pixels tall. *

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ @Override public void draw3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Draws an arc * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ @Override public void drawArc(int x, int y, int w, int h, int sa, int aa) { w = w >> 1; h = h >> 1; x += w; y += h; arc((double) x, (double) y, (double) w, (double) h, (double) -sa, (double) (-sa - aa), false); } /** *

* Not implemented

* * @param data a byte[] value * @param offset an int value * @param length an int value * @param x an int value * @param y an int value */ @Override public void drawBytes(byte[] data, int offset, int length, int x, int y) { } //============ Optimizers ======================= /** * @see Graphics2D#drawGlyphVector(GlyphVector, float, float) */ @Override public void drawGlyphVector(GlyphVector g, float x, float y) { Shape s = g.getOutline(x, y); fill(s); } /** * @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int) */ @Override public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { BufferedImage result = img; if (op != null) { result = op.createCompatibleDestImage(img, img.getColorModel()); result = op.filter(img, result); } drawImage(result, x, y, null); } /** * @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver) */ @Override public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { // return drawImage(img, null, xform, null, obs); return true; } /** *

* Draw's an image onto the page, with a backing colour.

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver obs) { return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), bgcolor, obs); } /** * Draw's an image onto the page * * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, ImageObserver obs) { return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs); } /** *

* Draw's an image onto the page, with a backing colour.

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, Color bgcolor, ImageObserver obs) { closeBlock(); pw.print("q "); // save state Color c = getColor(); // save current colour setColor(bgcolor); // change the colour drawRect(x, y, w, h); closeBlock("B Q"); // fill stroke, restore state paint = c; // restore original colour return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs); } /** *

* Draws an image onto the page.

* *

* This method is implemented with ASCIIbase85 encoding and the zip stream * deflater. It results in a stream that is anywhere from 3 to 10 times as * big as the image. This obviously needs some improvement, but it works * well for small images

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R"); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h); AffineTransform transformToSet = newTransform; pw.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

* This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver obs) { return false; } //============ Clipping operations ======================= /** * Draw's an image onto the page, with scaling *

* This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver obs) { // This shouldn't be too bad, just change the coordinate matrix return false; } /** * Draws a line between two coordinates. * * If the first coordinate is the same as the last one drawn (i.e. a * previous drawLine, moveto, etc) it is ignored. * * @param x1 coordinate * @param y1 coordinate * @param x2 coordinate * @param y2 coordinate */ @Override public void drawLine(int x1, int y1, int x2, int y2) { moveto(x1, y1); lineto(x2, y2); } //============ Arcs operations ============================== // These are the standard Graphics operators. They use the // arc extension operators to achieve the affect. /** *

* Draws an oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void drawOval(int x, int y, int w, int h) { drawArc(x, y, w, h, 0, 360); } /** * Draws a polygon, linking the first and last coordinates. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ @Override public void drawPolygon(int[] xp, int[] yp, int np) { polygon(xp, yp, np); closeBlock("s"); // close path and stroke } /** * Draws a polyline. The first and last coordinates are not linked. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polyline */ @Override public void drawPolyline(int[] xp, int[] yp, int np) { polygon(xp, yp, np); // no stroke, as we keep the optimizer in stroke state } /** * We override Graphics.drawRect as it doesn't join the 4 lines. Also, PDF * provides us with a Rectangle operator, so we will use that. * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void drawRect(int x, int y, int w, int h) { draw(new Rectangle(x, y, w, h)); /*newPath(); pw.print(cxy(x, y) + cwh(w, h) + "re "); // rectangle lx = x; // I don't know if this is correct, but lets see if PDF ends ly = y; // the rectangle at it's start. // stroke (optimized)*/ } /** * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform) */ @Override public void drawRenderableImage(RenderableImage img, AffineTransform xform) { drawRenderedImage(img.createDefaultRendering(), xform); } /** * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform) */ @Override public void drawRenderedImage(RenderedImage img, AffineTransform xform) { BufferedImage image = null; if (img instanceof BufferedImage) { image = (BufferedImage) img; } else { ColorModel cm = img.getColorModel(); int width = img.getWidth(); int height = img.getHeight(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); Hashtable properties = new Hashtable(); String[] keys = img.getPropertyNames(); if (keys != null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); image = result; } drawImage(image, xform, null); } /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ @Override public void drawRoundRect(int x, int y, int w, int h, int aw, int ah) { } //============ Oval operations ======================= /** * Draws a string using a AttributedCharacterIterator. *

* This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *

* This method is new to the Java2 API. */ @Override public void drawString(java.text.AttributedCharacterIterator aci, float x, float y) { } /** * Draws a string using a AttributedCharacterIterator. *

* This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *

* This method is new to the Java2 API. */ @Override public void drawString(java.text.AttributedCharacterIterator aci, int x, int y) { } public void drawStringWithMode(String s, float x, float y, int mode) { newTextBlock(x, y); if (mode > -1) { pw.println("" + mode + " Tr"); } if (pdffont instanceof PDFEmbeddedFont) { pw.print("[("); pw.printRaw(PDFStringHelper.makeRawPDFString(s)); pw.println(")] TJ"); } else { pw.print(PDFStringHelper.makePDFString(s)); pw.println(" Tj"); } closeBlock(); } @Override public void drawString(String s, float x, float y) { drawStringWithMode(s, x, y, -1); } /** * This draws a string. * * @param s * @oaran s String to draw * @param x coordinate * @param y coordinate */ @Override public void drawString(String s, int x, int y) { drawString(s, (float) x, (float) y); } public void drawTransparentString(String s, float x, float y) { drawStringWithMode(s, x, y, 3); } /** * This draws a transparent string. * * @oaran s String to draw * @param x coordinate * @param y coordinate */ public void drawTransparentString(String s, int x, int y) { drawTransparentString(s, (float) x, (float) y); } /** * @see Graphics2D#fill(Shape) */ @Override public void fill(Shape s) { followPath(s, FILL); } /** *

* Not implemented

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ @Override public void fill3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Fills an arc, joining the start and end coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ @Override public void fillArc(int x, int y, int w, int h, int sa, int aa) { // here we fool the optimizer. We force any open path to be closed, // then draw the arc. Finally, as the optimizer hasn't stroke'd the // path, we close and fill it, and mark the Stroke as closed. // // Note: The lineto to the centre of the object is required, because // the fill only fills the arc. Skipping this includes an extra // chord, which isn't correct. Peter May 31 2000 closeBlock(); patternFill(); drawArc(x, y, w, h, sa, aa); lineto(x + (w >> 1), y + (h >> 1)); if (shadingFill()) { return; } closeBlock("b"); // closepath and fill } //============ Extension operations ============================== // These are extensions, and provide access to PDF Specific // operators. /** *

* Draws a filled oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void fillOval(int x, int y, int w, int h) { fillArc(x, y, w, h, 0, 360); } //============ Polygon operations ======================= /** * Fills a polygon. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ @Override public void fillPolygon(int[] xp, int[] yp, int np) { closeBlock(); // finish off any previous paths patternFill(); polygon(xp, yp, np); if (shadingFill()) { return; } closeBlock("b"); // closepath, fill and stroke } //============ Image operations ======================= /** * Fills a rectangle with the current colour * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void fillRect(int x, int y, int w, int h) { fill(new Rectangle(x, y, w, h)); /* // end any path & stroke. This ensures the fill is on this // rectangle, and not on any previous graphics closeBlock(); patternFill(); drawRect(x, y, w, h); if (shadingFill()) { return; } closeBlock("B"); // rectangle, fill stroke*/ } private void patternFill() { if (pattern != null) { pw.println("/Pattern cs"); pw.println(pattern + " scn"); } } private boolean shadingFill() { if (pattern == null && shading != null) { saveState(); pw.println("W n"); pw.println(shading + " sh"); restoreState(); return true; } return false; } //============ Round Rectangle operations ======================= /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ @Override public void fillRoundRect(int x, int y, int w, int h, int aw, int ah) { } /////////////////////////////////////////////// // // // implementation specific methods // // private void followPath(Shape s, int drawType) { PathIterator points; if (s == null) { return; } if (drawType == FILL) { patternFill(); } if (drawType == STROKE) { if (!(stroke instanceof BasicStroke)) { s = stroke.createStrokedShape(s); followPath(s, FILL); return; } } // if (drawType==STROKE) { // setStrokeDiff(stroke, oldStroke); // oldStroke = stroke; // setStrokePaint(); // } // else if (drawType==FILL) // setFillPaint(); points = s.getPathIterator(IDENTITY); int segments = 0; float[] coords = new float[6]; while (!points.isDone()) { segments++; int segtype = points.currentSegment(coords); switch (segtype) { case PathIterator.SEG_CLOSE: pw.print("h "); break; case PathIterator.SEG_CUBICTO: curveto(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case PathIterator.SEG_LINETO: lineto(coords[0], coords[1]); break; case PathIterator.SEG_MOVETO: moveto(coords[0], coords[1]); break; case PathIterator.SEG_QUADTO: curveto(coords[0], coords[1], coords[2], coords[3]); break; } points.next(); } switch (drawType) { case FILL: if (segments > 0) { if (pattern == null && shading != null) { saveState(); if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("W*"); } else { closeBlock("W"); } pw.println("n"); pw.println(shading + " sh"); restoreState(); return; } if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("B*"); } else { closeBlock("B"); } } break; case STROKE: if (segments > 0) { closeBlock("S"); } break; case CLIP: default: //drawType==CLIP if (segments == 0) { drawRect(0, 0, 0, 0); } if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("W*"); } else { closeBlock("W"); } } } /** * @see Graphics2D#getBackground() */ @Override public Color getBackground() { return background; } /** * Returns the Shape of the clipping region As my JDK docs say, this may * break with Java 2D. * * @return Shape of the clipping region */ @Override public Shape getClip() { return null; } /** * Returns the Rectangle that fits the current clipping region * * @return the Rectangle that fits the current clipping region */ @Override public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ @Override public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ @Override public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * * @return the current font. */ @Override public Font getFont() { if (font == null) { setFont(new Font("SansSerif", Font.PLAIN, 12)); } return font; } /** * Returns the FontMetrics for a font. *

* This doesn't work correctly. Perhaps having some way of mapping the base * 14 fonts to our own FontMetrics implementation? * * @param font The java.awt.Font to return the metrics for * @return FontMetrics for a font */ @Override public FontMetrics getFontMetrics(Font font) { Frame dummy = new Frame(); dummy.addNotify(); Image image = dummy.createImage(100, 100); if (image == null) { System.err.println("getFontMetrics: image is null"); } Graphics graphics = image.getGraphics(); return graphics.getFontMetrics(font); } /** * @see Graphics2D#getFontRenderContext() */ @Override public FontRenderContext getFontRenderContext() { boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS)); return new FontRenderContext(new AffineTransform(), antialias, fractions); } /** * Returns the associated PDFPage for this graphic * * @return the associated PDFPage for this graphic */ public PDFPage getPage() { return page; } /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Paint getPaint() { return paint; } /** * @param arg0 a key * @return the rendering hint */ @Override public Object getRenderingHint(Key arg0) { return rhints.get(arg0); } /** * @see Graphics2D#getRenderingHints() */ @Override public RenderingHints getRenderingHints() { return rhints; } /** * @see Graphics2D#getStroke() */ @Override public Stroke getStroke() { return stroke; } /** * @see Graphics2D#getTransform() */ @Override public AffineTransform getTransform() { return new AffineTransform(transform); } /** * Returns the PrintWriter handling the underlying stream * * @return the PrintWriter handling the underlying stream */ public RawPrintWriter getWriter() { return pw; } /** * @see Graphics2D#hit(Rectangle, Shape, boolean) */ @Override public boolean hit(Rectangle rect, Shape s, boolean onStroke) { if (onStroke) { s = stroke.createStrokedShape(s); } Area area = new Area(s); if (clip != null) { area.intersect(clip); } return area.intersects(rect.x, rect.y, rect.width, rect.height); } /** * This initialises the stream by saving the current graphics state, and * setting up the default line width (for us). * * It also sets up the instance ready for graphic operations and any * optimisations. * *

* For child instances, the stream is already open, so this should keep * things happy. */ private void init() { PageFormat pf = page.getPageFormat(); // save graphics state (restored by dispose) if (child) { pw.print("q "); } // now initialise the instance //setColor(Color.black); paint = Color.black; // possible: if parent.color is not black, then force black? // must check to see what AWT does? // Original User Space Transform (identity) // Transform from Java Space to PDF Space pTransform = new AffineTransform(); pTransform.translate(0, pf.getHeight()); pTransform.scale(1d, -1d); // Combined Transform User->Java->PDF setNewTranform(new AffineTransform()); // Set the line width setStroke(DEF_STROKE); } /** * This is called by PDFPage when creating a Graphcis instance. * * @param page The PDFPage to draw onto. */ protected void init(PDFPage page) { this.page = page; // We are the parent instance child = false; // Now create a stream to store the graphics in PDFStream stream = new PDFStream(); // To view detail in uncompressed format comment out the next line stream.setDeflate(true); page.getPDFDocument().add(stream); page.add(stream); pw = new RawPrintWriter(stream.getOutputStream()); // initially, we are limited to the page size clipRectangle = page.getImageableArea(); // finally initialise the stream init(); } /** * This method is used internally by create() and by the PDFJob class * * @param page PDFPage to draw into * @param pw PrintWriter to use */ protected void init(PDFPage page, RawPrintWriter pw) { this.page = page; this.pw = pw; // In this case, we didn't create the stream (our parent did) // so child is true (see dispose) child = true; // finally initialise the stream init(); } /** * This adds a line segment to the current path * * @param x coordinate * @param y coordinate */ public void lineto(double x, double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x, y) + "l "); lx = (float) x; ly = (float) y; } /** * This adds a line segment to the current path * * @param x coordinate * @param y coordinate */ public void lineto(int x, int y) { newPath(); if (lx != x && ly != y) { pw.print(cxy(x, y) + "l "); } lx = x; ly = y; } /** * This moves the current drawing point. * * @param x coordinate * @param y coordinate */ public void moveto(double x, double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x, y) + "m "); lx = (float) x; ly = (float) y; } /** * This moves the current drawing point. * * @param x coordinate * @param y coordinate */ public void moveto(int x, int y) { newPath(); if (lx != x || ly != y) { pw.print(cxy(x, y) + "m "); } lx = x; ly = y; } /** * Functions that draw lines should start by calling this. It starts a new * path unless inStroke is set, in that case it uses the existing path */ void newPath() { if (inText) { closeBlock(); } if (!inStroke) { if (pre_np != null) { pw.print(pre_np); // this is the prefix set by setOrientation() pre_np = null; } pw.print("n "); } inText = false; inStroke = true; // an unlikely coordinate to fool the moveto() optimizer lx = ly = -9999; } /** *

* Functions that draw text should start by calling this. It starts a text * block (accounting for media orientation) unless we are already in a Text * block.

* *

* It also handles if the font has been changed since the current text block * was started, so your function will be current.

* * @param x x coordinate in java space * @param y y coordinate in java space */ void newTextBlock(float x, float y) { // close the current path if there is one if (inStroke) { closeBlock(); } // create the text block if one is not current. If we are, the newFont // condition at the end catches font changes if (!inText) { // This ensures that there is a font available getFont(); pw.print("q BT "); tx = ty = 0; AffineTransform tm = new AffineTransform(pTransform); pw.println("" + df.format(tm.getScaleX()) + " " + "" + df.format(tm.getShearY()) + " " + "" + df.format(tm.getShearX()) + " " + "" + df.format(tm.getScaleY()) + " " + "" + df.format(tm.getTranslateX()) + " " + "" + df.format(tm.getTranslateY()) + " Tm" ); // produce the text matrix for the media // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait // //pw.println("1 0 0 1 0 0 Tm"); // break; // // case PageFormat.LANDSCAPE: // Landscape // pw.println("0 1 -1 0 0 0 Tm"); // rotate // break; // // case 180: // Inverted Portrait // pw.println("1 0 0 -1 0 0 Tm"); // break; // // case PageFormat.REVERSE_LANDSCAPE: // Seascape // pw.println("0 -1 1 0 0 0 Tm"); // rotate // break; // } // move the text cursor by an absolute amount pw.print(txy(x, y) + "Td "); } else { // move the text cursor by a relative amount pw.print(twh(x, y, tx, ty) + "Td "); //pw.print(txy(x,y)+"Td "); } // preserve the coordinates for the next time tx = x; ty = y; if (newFont || !inText) { pw.print(pdffont.getName() + " " + font.getSize() + " Tf "); } // later add colour changes here (if required) inStroke = newFont = false; inText = true; } /** * This is used to add a polygon to the current path. Used by drawPolygon(), * drawPolyline() and fillPolygon() etal * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon * @see #drawPolygon * @see #drawPolyline * @see #fillPolygon */ public void polygon(int[] xp, int[] yp, int np) { // newPath() not needed here as moveto does it ;-) moveto(xp[0], yp[0]); for (int i = 1; i < np; i++) { lineto(xp[i], yp[i]); } } /** * @see Graphics2D#rotate(double) */ @Override public void rotate(double theta) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta); setNewTranform(newTransform); } /** * @see Graphics2D#rotate(double, double, double) */ @Override public void rotate(double theta, double x, double y) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta, x, y); setNewTranform(newTransform); } /** * @see Graphics2D#scale(double, double) */ @Override public void scale(double sx, double sy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.scale(sx, sy); setNewTranform(newTransform); } /** * @see Graphics2D#setBackground(Color) */ @Override public void setBackground(Color color) { background = color; } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void setClip(int x, int y, int w, int h) { /*clipRectangle = new Rectangle(x, y, w, h); closeBlock(); // finish off any existing paths drawRect(x, y, w, h); closeBlock("W n"); // clip to current path*/ setClip(new Rectangle(x, y, w, h)); } /** * As my JDK docs say, this may break with Java 2D. *

* Sets the clipping region to that of a Shape. * * @param s Shape to clip to. */ @Override public void setClip(Shape s) { closeBlock(); if (clip != null) { restoreState(); transform = clipTransform; } if (s == null) { clip = null; return; } clipTransform = transform; clip = new Area(s); clipRectangle = s.getBounds(); saveState(); followPath(s, CLIP); pw.println("n"); //setClip(r.x, r.y, r.width, r.height); } /** * Sets the color for drawing * * @param c Color to use */ @Override public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ @Override public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * * @param f java.awt.Font to set to. */ @Override public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if (font != f) { font = f; pdffont = page.getFont("/Type1", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println("" + lineCap + " J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println("" + lineJoin + " j"); } } /** * This extension allows the width of the drawn line to be set * * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println("" + width + " w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println("" + limit + " M"); } } /** * Sets the paint for drawing * * @param paint Paint to use */ @Override public void setPaint(Paint paint) { this.paint = paint; this.shading = null; this.pattern = null; if (paint instanceof Color) { Color c = (Color) paint; double r = ((double) c.getRed()) / 255.0; double g = ((double) c.getGreen()) / 255.0; double b = ((double) c.getBlue()) / 255.0; closeBlock(); // This ensures any paths are drawn in the previous if (currentAlpha != c.getAlpha()) { String gsId = "/GSAlpha" + c.getAlpha(); currentAlpha = c.getAlpha(); if (!usedAlphas.contains(c.getAlpha())) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } // colours pw.println("" + r + " " + g + " " + b + " rg " + r + " " + g + " " + b + " RG"); } if (paint instanceof MultipleGradientPaint) { closeBlock(); MultipleGradientPaint grad = (MultipleGradientPaint) paint; /*if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D start = linGrad.getStartPoint(); Point2D end = linGrad.getEndPoint(); double deltaX = end.getX() - start.getX(); double deltaY = end.getY() - start.getY(); Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY); int colorCount = grad.getFractions().length; float fractions2[] = new float[colorCount * 2 - 1]; Color colors2[] = new Color[colorCount * 2 - 1]; for (int i = 0; i < colorCount; i++) { colors2[i] = linGrad.getColors()[i]; fractions2[i] = linGrad.getFractions()[i] / 2; } for (int i = 0; i < colorCount; i++) { colors2[colors2.length - i - 1] = linGrad.getColors()[i]; fractions2[colors2.length - i - 1] = 1f - linGrad.getFractions()[i] / 2; } LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT); grad = linGrad2; }*/ List functions2Refs = new ArrayList<>(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = grad.getColors()[i - 1]; final Color color2 = grad.getColors()[i]; PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getRed()) / 255.0f) + " " + (((float) color1.getGreen()) / 255.0f) + " " + (((float) color1.getBlue()) / 255.0f) + "] /C1 [" + (((float) color2.getRed()) / 255.0f) + " " + (((float) color2.getGreen()) / 255.0f) + " " + (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes()); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 3 /Domain [0 1] /Functions [" + String.join(" ", functions2Refs) + "] ").getBytes()); int lastcols = fgrad.getColors().length - 1; List bounds = new ArrayList<>(); List encode = new ArrayList<>(); for (int i = 1; i < fgrad.getColors().length; i++) { if (i < lastcols) { bounds.add("" + fgrad.getFractions()[i]); } encode.add("0 1"); } os.write(("/Bounds [" + String.join(" ", bounds) + "] ").getBytes()); os.write(("/Encode [" + String.join(" ", encode) + "]\n").getBytes()); writeEnd(os); } }; page.getPDFDocument().add(function3); PDFObject shading = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); if (fgrad instanceof LinearGradientPaint) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; MyDoubleRect coords; //if (linGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { coords = new MyDoubleRect(linGrad.getStartPoint().getX(), linGrad.getStartPoint().getY(), linGrad.getEndPoint().getX(), linGrad.getEndPoint().getY()); /*} else { double deltaX = linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX(); double deltaY = linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY(); double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY); coords = new MyDoubleRect(0, 0, 0, len); }*/ os.write(("/ShadingType 2 /ColorSpace /DeviceRGB " + "/Coords [" + coords.xMin + " " + coords.yMin + " " + coords.xMax + " " + coords.yMax + "] " + "/Domain [0 1] " + "/Function " + function3.getSerialID() + " 0 R /Extend [true true]\n").getBytes()); writeEnd(os); } if (fgrad instanceof RadialGradientPaint) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; os.write(("/ShadingType 3 /ColorSpace /DeviceRGB " + "/Coords [" + radGrad.getFocusPoint().getX() + " " + radGrad.getFocusPoint().getY() + " " + "0 " + radGrad.getCenterPoint().getX() + " " + radGrad.getCenterPoint().getY() + " " + radGrad.getRadius() + "] " + "/Domain [0 1] " + "/Function " + function3.getSerialID() + " 0 R /Extend [true true]\n").getBytes()); writeEnd(os); } } }; page.getPDFDocument().add(shading); shadingCount++; /*if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; double deltaX = linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX(); double deltaY = linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY); AffineTransform m = AffineTransform.getRotateInstance(alfa); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); final int fCurrentShadingCount = shadingCount; PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); double w = Math.abs(linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX()); double h = Math.abs(linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY()); if (w < 1) { w = 1; } if (h < 1) { h = 1; } w = 1; h = len; os.write("/PatternType 1\n".getBytes()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes()); os.write(("/XStep " + w + "\n").getBytes()); os.write(("/YStep " + h + "\n").getBytes()); os.write(("/Resources << " // + "/Pattern << /pin" + shadingCount + " " + pattern.getSerialID() + " 0 R >>" + "/Shading << /Shin" + fCurrentShadingCount + " " + shading.getSerialID() + " 0 R >>" + ">>\n").getBytes()); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/Shin" + shadingCount + " sh").getBytes()); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(innerPattern); page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; return; }*/ //page.addPatternResource("/p" + shadingCount + " " + pattern.getSerialID() + " 0 R"); page.addShadingResource("/Sh" + shadingCount + " " + shading.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } } /** * Not implemented, as this is not supported in the PDF specification. */ @Override public void setPaintMode() { } /** * Sets a rendering hint * * @param arg0 * @param arg1 */ @Override public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ @Override public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ @Override public void setStroke(Stroke s) { this.stroke = s; if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ @Override public void setTransform(AffineTransform t) { setNewTranform(new AffineTransform(t)); } /** * Not implemented, as this is not supported in the PDF specification. * * @param c1 Color to xor with */ @Override public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ @Override public void shear(double shx, double shy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.shear(shx, shy); setNewTranform(newTransform); } /** * @see Graphics2D#transform(AffineTransform) */ @Override public void transform(AffineTransform tx) { AffineTransform newTransform = new AffineTransform(transform); newTransform.concatenate(tx); setNewTranform(newTransform); } /** * @see Graphics2D#translate(double, double) */ @Override public void translate(double tx, double ty) { AffineTransform newTransform = new AffineTransform(transform); newTransform.translate(tx, ty); setNewTranform(newTransform); } /** * @see Graphics#translate(int, int) */ @Override public void translate(int x, int y) { translate((double) x, (double) y); } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x, float y, float tx, float ty) { float nx = x, ny = y; float ntx = tx, nty = ty; nx = (float) (x - tx); ny = (float) (y - ty); return "" + df.format(nx) + " " + df.format(ny) + " "; } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x, float y) { Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); pTransform.transform(ptSrc, ptDst); return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " "; } private void setNewTranform(AffineTransform t) { closeBlock(); AffineTransform newTransform = new AffineTransform(t); AffineTransform transformToSet = new AffineTransform(newTransform); if (transform != null) { AffineTransform aInv = new AffineTransform(transform); try { aInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } AffineTransform pInv = new AffineTransform(pTransform); try { pInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } transformToSet = new AffineTransform(); transformToSet.concatenate(aInv); transformToSet.concatenate(pInv); transformToSet.concatenate(pTransform); transformToSet.concatenate(newTransform); } else { transformToSet.preConcatenate(pTransform); } transform = newTransform; pw.println("" + matDf.format(transformToSet.getScaleX()) + " " + "" + matDf.format(transformToSet.getShearY()) + " " + "" + matDf.format(transformToSet.getShearX()) + " " + "" + matDf.format(transformToSet.getScaleY()) + " " + "" + matDf.format(transformToSet.getTranslateX()) + " " + "" + matDf.format(transformToSet.getTranslateY()) + " cm" ); } private void saveState() { pw.println("q"); } private void restoreState() { pw.println("Q"); } } // end class PDFGraphics \ No newline at end of file +/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gnu.jpdf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.LinearGradientPaint; import java.awt.MultipleGradientPaint; import java.awt.Paint; import java.awt.Polygon; import java.awt.RadialGradientPaint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.Shape; import java.awt.Stroke; import java.awt.TexturePaint; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.awt.print.PageFormat; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Serializable; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * This class is our implementation of AWT's Graphics class. It provides a Java * standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */ public class PDFGraphics extends Graphics2D implements Serializable { /** * One degree in radians */ private static final double degrees_to_radians = Math.PI / 180.0; private static final int FILL = 1; private static final int STROKE = 2; private static final int CLIP = 3; private static final AffineTransform IDENTITY = new AffineTransform(); private static final Stroke DEF_STROKE = new BasicStroke(); /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.pdf. * The formatting was changed a little bit. * This used to subclass an abstract class in a different package with * the same name (confusing). Now it's one concrete class. * drawImage() was implemented * It is still licensed under the LGPL. */ // Implementation notes: // // Pages 333-335 of the PDF Reference Manual // // Unless absolutely required, use the moveto, lineto and rectangle // operators to perform those actions. // They contain some extra optimizations // which will reduce the output size by up to half in some cases. // // About fill operators: For correct operation, any fill operation should // start with closeBlock(), which will ensure any previous path is completed, // otherwise you may find the fill will include previous items private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH)); private static final DecimalFormat matDf = new DecimalFormat("0", new DecimalFormatSymbols(Locale.ENGLISH)); static { matDf.setMaximumFractionDigits(340); } //JPEXS: cache for already used images private static Map usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * * @see #create */ private boolean child; private Area clip; private AffineTransform clipTransform; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: The last known moveto/lineto x coordinate * * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: The last known moveto/lineto y coordinate * * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; private AffineTransform paintTransform; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: This is written to the stream when the newPath() * is called. np then clears this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties private AffineTransform transform; /** * Part of the optimizer: The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering private String shading = null; private String pattern = null; private Set usedAlphas = new HashSet<>(); private int currentAlpha = 255; private int shadingCount = 0; /** * @see Graphics2D#addRenderingHints(Map) */ @Override public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc, double ayc, double width, double height, double ang1, double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1 % 360.0) * degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width * cos0; y0 = ayc + height * sin0; // NB: !clockwise here as Java Space is inverted to User Space if (!clockwise) { // Quadrant reduction while (ang1 < ang2) { ang2 -= 360.0; } while ((adiff = ang2 - ang1) < -90.0) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while (ang2 < ang1) { ang2 += 360.0; } while ((adiff = ang2 - ang1) > 90.0) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width * sin0; double yt = y0 + trad * height * cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path Important: We * write directly to the stream here, because this method operates in User * space, rather than Java space. * * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w, double h, double x0, double y0, double x3, double y3, double xt, double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx * dx + dy * dy; double w2 = w * w, h2 = h * h; double r2 = w2 + h2; double fw = 0.0, fh = 0.0; if (dist < (r2 * 1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0; } // The path must have a starting point if (first) { moveto(x0, y0); } double x = x0 + ((xt - x0) * fw); double y = y0 + ((yt - y0) * fh); x0 = x3 + ((xt - x3) * fw); y0 = y3 + ((yt - y3) * fh); // Finally the actual curve. curveto(x, y, x0, y0, x3, y3); } /** * This simply draws a White Rectangle to clear the area * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clearRect(int x, int y, int w, int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x, y, w, h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ @Override public void clip(Shape s) { if (s == null) { setClip(null); return; } Area newClip; if (clip == null) { newClip = new Area(s); } else { newClip = (Area) clip.clone(); newClip.intersect(new Area(s)); } setClip(newClip); } /** * This extra method allows PDF users to clip to a Polygon. * *

* In theory you could use setClip(), except that java.awt.Graphics only * supports Rectangle with that method, so we will have an extra method. * * @param p Polygon to clip to */ public void clipPolygon(Polygon p) { closeBlock(); // finish off any existing path polygon(p.xpoints, p.ypoints, p.npoints); closeBlock("W"); // clip to current path clipRectangle = p.getBounds(); } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clipRect(int x, int y, int w, int h) { setClip(x, y, w, h); } /** * All functions should call this to close any existing optimized blocks. */ void closeBlock() { closeBlock("S"); } /** *

* This is used by code that use the path in any way other than Stroke (like * Fill, close path & Stroke etc). Usually this is used internally.

* * @param code PDF operators that will close the path */ void closeBlock(String code) { if (inText) { pw.println("ET Q"); // setOrientation(); // fixes Orientation matrix } if (inStroke) { pw.println(code); } inStroke = inText = false; } /** * This is unsupported - how do you do this with Vector graphics? * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param dx coordinate * @param dy coordinate */ @Override public void copyArea(int x, int y, int w, int h, int dx, int dy) { // Hmm... Probably need to keep track of everything // that has been drawn so far to get the contents of an area } //============ Line operations ======================= /** *

* This returns a child instance of this Graphics object. As with AWT, the * affects of using the parent instance while the child exists, is not * determined.

* *

* Once complete, the child should be released with it's dispose() method * which will restore the graphics state to it's parent.

* * @return Graphics object to render onto the page */ @Override public Graphics create() { closeBlock(); PDFGraphics g = createGraphic(page, pw); // The new instance inherits a few items g.clipRectangle = new Rectangle(clipRectangle); return (Graphics) g; } // end create() /** * This method creates a new instance of the class based on the page and a * print writer. * * @param page the page to attach to * @param pw the PrintWriter to attach to. */ protected PDFGraphics createGraphic(PDFPage page, RawPrintWriter pw) { PDFGraphics g = new PDFGraphics(); g.init(page, pw); return g; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using the current point and (x1,y1) as the * Bezier control points. *

* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(double x1, double y1, double x2, double y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "v"); lx = (float) x2; ly = (float) y2; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier * control points. *

* The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(double x1, double y1, double x2, double y2, double x3, double y3) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c"); lx = (float) x3; ly = (float) y3; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using the current point and (x1,y1) as the * Bezier control points. *

* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto(int x1, int y1, int x2, int y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "v"); lx = x2; ly = y2; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x3,y3) using (x1,y1) and (x2,y2) as the Bezier * control points. *

* The new current point is (x3,y3) * * @param x1 First control point * @param y1 First control point * @param x2 Second control point * @param y2 Second control point * @param x3 Destination point * @param y3 Destination point */ public void curveto(int x1, int y1, int x2, int y2, int x3, int y3) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + cxy(x3, y3) + "c"); lx = x3; ly = y3; } /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using (x1,y1) and the end point as the * Bezier control points. *

* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(double x1, double y1, double x2, double y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "y"); lx = (float) x2; ly = (float) y2; } // Arcs are horrible and complex. They are at the end of the // file, because they are the largest. This is because, unlike // Postscript, PDF doesn't have any arc operators, so we must // implement them by converting into one or more Bezier curves // (which is how Postscript does them internally). /** * This extension appends a Bezier curve to the path. The curve extends from * the current point to (x2,y2) using (x1,y1) and the end point as the * Bezier control points. *

* The new current point is (x2,y2) * * @param x1 Second control point * @param y1 Second control point * @param x2 Destination point * @param y2 Destination point */ public void curveto2(int x1, int y1, int x2, int y2) { newPath(); pw.println(cxy(x1, y1) + cxy(x2, y2) + "y"); lx = x2; ly = y2; } /** * Converts the Java space dimension into pdf. * * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(double w, double h) { return "" + df.format(w) + " " + df.format(h) + " "; } /** * Converts the Java space dimension into pdf. * * @param w width * @param h height * @return String containing the coordinates in PDF space */ private String cwh(int w, int h) { return cwh((double) w, (double) h); } /** * Converts the Java space coordinates into pdf. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(double x, double y) { return "" + df.format(x) + " " + df.format(y) + " "; } /** * Converts the Java space coordinates into pdf. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF space */ private String cxy(int x, int y) { return cxy((double) x, (double) y); } /** *

* This releases any resources used by this Graphics object. You must use * this method once finished with it. Leaving it open will leave the PDF * stream in an inconsistent state, and will produce errors.

* *

* If this was created with Graphics.create() then the parent instance can * be used again. If not, then this closes the graphics operations for this * page when used with PDFJob.

* *

* When using PDFPage, you can create another fresh Graphics instance, which * will draw over this one.

* */ @Override public void dispose() { closeBlock(); if (clip != null) { restoreState(); } if (child) { pw.println("Q"); // restore graphics context } else { pw.close(); // close the stream if were the parent } } // ********************************************* // **** Implementation of java.awt.Graphics **** // ********************************************* //============ Rectangle operations ======================= /** * @see Graphics2D#draw(Shape) */ @Override public void draw(Shape s) { followPath(s, STROKE); } /** *

* Not implemented

* *

* Draws a 3-D highlighted outline of the specified rectangle. The edges of * the rectangle are highlighted so that they appear to be beveled and lit * from the upper left corner. The colors used for the highlighting effect * are determined based on the current color. The resulting rectangle covers * an area that is width + 1 pixels wide by height + 1 pixels tall. *

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ @Override public void draw3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Draws an arc * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ @Override public void drawArc(int x, int y, int w, int h, int sa, int aa) { w = w >> 1; h = h >> 1; x += w; y += h; arc((double) x, (double) y, (double) w, (double) h, (double) -sa, (double) (-sa - aa), false); } /** *

* Not implemented

* * @param data a byte[] value * @param offset an int value * @param length an int value * @param x an int value * @param y an int value */ @Override public void drawBytes(byte[] data, int offset, int length, int x, int y) { } //============ Optimizers ======================= /** * @see Graphics2D#drawGlyphVector(GlyphVector, float, float) */ @Override public void drawGlyphVector(GlyphVector g, float x, float y) { Shape s = g.getOutline(x, y); fill(s); } /** * @see Graphics2D#drawImage(BufferedImage, BufferedImageOp, int, int) */ @Override public void drawImage(BufferedImage img, BufferedImageOp op, int x, int y) { BufferedImage result = img; if (op != null) { result = op.createCompatibleDestImage(img, img.getColorModel()); result = op.filter(img, result); } drawImage(result, x, y, null); } /** * @see Graphics2D#drawImage(Image, AffineTransform, ImageObserver) */ @Override public boolean drawImage(Image img, AffineTransform xform, ImageObserver obs) { // return drawImage(img, null, xform, null, obs); return true; } /** *

* Draw's an image onto the page, with a backing colour.

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, Color bgcolor, ImageObserver obs) { return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), bgcolor, obs); } /** * Draw's an image onto the page * * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, ImageObserver obs) { return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs); } /** *

* Draw's an image onto the page, with a backing colour.

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, Color bgcolor, ImageObserver obs) { closeBlock(); pw.print("q "); // save state Color c = getColor(); // save current colour setColor(bgcolor); // change the colour drawRect(x, y, w, h); closeBlock("B Q"); // fill stroke, restore state paint = c; // restore original colour return drawImage(img, x, y, img.getWidth(obs), img.getHeight(obs), obs); } /** *

* Draws an image onto the page.

* *

* This method is implemented with ASCIIbase85 encoding and the zip stream * deflater. It results in a stream that is anywhere from 3 to 10 times as * big as the image. This obviously needs some improvement, but it works * well for small images

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R"); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h); AffineTransform transformToSet = newTransform; pw.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

* This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param bgcolor Background colour * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, Color bgcolor, ImageObserver obs) { return false; } //============ Clipping operations ======================= /** * Draw's an image onto the page, with scaling *

* This is not yet supported. * * @param img The java.awt.Image * @param dx1 coordinate on page * @param dy1 coordinate on page * @param dx2 coordinate on page * @param dy2 coordinate on page * @param sx1 coordinate on image * @param sy1 coordinate on image * @param sx2 coordinate on image * @param sy2 coordinate on image * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int dx1, int dy1, int dx2, int dy2, int sx1, int sy1, int sx2, int sy2, ImageObserver obs) { // This shouldn't be too bad, just change the coordinate matrix return false; } /** * Draws a line between two coordinates. * * If the first coordinate is the same as the last one drawn (i.e. a * previous drawLine, moveto, etc) it is ignored. * * @param x1 coordinate * @param y1 coordinate * @param x2 coordinate * @param y2 coordinate */ @Override public void drawLine(int x1, int y1, int x2, int y2) { moveto(x1, y1); lineto(x2, y2); } //============ Arcs operations ============================== // These are the standard Graphics operators. They use the // arc extension operators to achieve the affect. /** *

* Draws an oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void drawOval(int x, int y, int w, int h) { drawArc(x, y, w, h, 0, 360); } /** * Draws a polygon, linking the first and last coordinates. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ @Override public void drawPolygon(int[] xp, int[] yp, int np) { polygon(xp, yp, np); closeBlock("s"); // close path and stroke } /** * Draws a polyline. The first and last coordinates are not linked. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polyline */ @Override public void drawPolyline(int[] xp, int[] yp, int np) { polygon(xp, yp, np); // no stroke, as we keep the optimizer in stroke state } /** * We override Graphics.drawRect as it doesn't join the 4 lines. Also, PDF * provides us with a Rectangle operator, so we will use that. * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void drawRect(int x, int y, int w, int h) { draw(new Rectangle(x, y, w, h)); /*newPath(); pw.print(cxy(x, y) + cwh(w, h) + "re "); // rectangle lx = x; // I don't know if this is correct, but lets see if PDF ends ly = y; // the rectangle at it's start. // stroke (optimized)*/ } /** * @see Graphics2D#drawRenderableImage(RenderableImage, AffineTransform) */ @Override public void drawRenderableImage(RenderableImage img, AffineTransform xform) { drawRenderedImage(img.createDefaultRendering(), xform); } /** * @see Graphics2D#drawRenderedImage(RenderedImage, AffineTransform) */ @Override public void drawRenderedImage(RenderedImage img, AffineTransform xform) { BufferedImage image = null; if (img instanceof BufferedImage) { image = (BufferedImage) img; } else { ColorModel cm = img.getColorModel(); int width = img.getWidth(); int height = img.getHeight(); WritableRaster raster = cm.createCompatibleWritableRaster(width, height); boolean isAlphaPremultiplied = cm.isAlphaPremultiplied(); Hashtable properties = new Hashtable(); String[] keys = img.getPropertyNames(); if (keys != null) { for (int i = 0; i < keys.length; i++) { properties.put(keys[i], img.getProperty(keys[i])); } } BufferedImage result = new BufferedImage(cm, raster, isAlphaPremultiplied, properties); img.copyData(raster); image = result; } drawImage(image, xform, null); } /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ @Override public void drawRoundRect(int x, int y, int w, int h, int aw, int ah) { } //============ Oval operations ======================= /** * Draws a string using a AttributedCharacterIterator. *

* This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *

* This method is new to the Java2 API. */ @Override public void drawString(java.text.AttributedCharacterIterator aci, float x, float y) { } /** * Draws a string using a AttributedCharacterIterator. *

* This is not supported yet, as I have no idea what an * AttributedCharacterIterator is. *

* This method is new to the Java2 API. */ @Override public void drawString(java.text.AttributedCharacterIterator aci, int x, int y) { } public void drawStringWithMode(String s, float x, float y, int mode) { newTextBlock(x, y); if (mode > -1) { pw.println("" + mode + " Tr"); } if (pdffont instanceof PDFEmbeddedFont) { pw.print("[("); pw.printRaw(PDFStringHelper.makeRawPDFString(s)); pw.println(")] TJ"); } else { pw.print(PDFStringHelper.makePDFString(s)); pw.println(" Tj"); } closeBlock(); } @Override public void drawString(String s, float x, float y) { drawStringWithMode(s, x, y, -1); } /** * This draws a string. * * @param s * @oaran s String to draw * @param x coordinate * @param y coordinate */ @Override public void drawString(String s, int x, int y) { drawString(s, (float) x, (float) y); } public void drawTransparentString(String s, float x, float y) { drawStringWithMode(s, x, y, 3); } /** * This draws a transparent string. * * @oaran s String to draw * @param x coordinate * @param y coordinate */ public void drawTransparentString(String s, int x, int y) { drawTransparentString(s, (float) x, (float) y); } /** * @see Graphics2D#fill(Shape) */ @Override public void fill(Shape s) { followPath(s, FILL); } /** *

* Not implemented

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ @Override public void fill3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Fills an arc, joining the start and end coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ @Override public void fillArc(int x, int y, int w, int h, int sa, int aa) { // here we fool the optimizer. We force any open path to be closed, // then draw the arc. Finally, as the optimizer hasn't stroke'd the // path, we close and fill it, and mark the Stroke as closed. // // Note: The lineto to the centre of the object is required, because // the fill only fills the arc. Skipping this includes an extra // chord, which isn't correct. Peter May 31 2000 closeBlock(); patternFill(); drawArc(x, y, w, h, sa, aa); lineto(x + (w >> 1), y + (h >> 1)); if (shadingFill()) { return; } closeBlock("b"); // closepath and fill } //============ Extension operations ============================== // These are extensions, and provide access to PDF Specific // operators. /** *

* Draws a filled oval

* * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void fillOval(int x, int y, int w, int h) { fillArc(x, y, w, h, 0, 360); } //============ Polygon operations ======================= /** * Fills a polygon. * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon */ @Override public void fillPolygon(int[] xp, int[] yp, int np) { closeBlock(); // finish off any previous paths patternFill(); polygon(xp, yp, np); if (shadingFill()) { return; } closeBlock("b"); // closepath, fill and stroke } //============ Image operations ======================= /** * Fills a rectangle with the current colour * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void fillRect(int x, int y, int w, int h) { fill(new Rectangle(x, y, w, h)); /* // end any path & stroke. This ensures the fill is on this // rectangle, and not on any previous graphics closeBlock(); patternFill(); drawRect(x, y, w, h); if (shadingFill()) { return; } closeBlock("B"); // rectangle, fill stroke*/ } private void patternFill() { if (pattern != null) { if (paint instanceof TexturePaint) { if (pattern.equals("texture") || !paintTransform.equals(transform)) { initTexturePaint((TexturePaint) paint); paintTransform = transform; } } pw.println("/Pattern cs"); pw.println(pattern + " scn"); } } private boolean shadingFill() { if (pattern == null && shading != null) { saveState(); pw.println("W n"); pw.println(shading + " sh"); restoreState(); return true; } return false; } //============ Round Rectangle operations ======================= /** * This is not yet implemented * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param aw a-width * @param ah a-height */ @Override public void fillRoundRect(int x, int y, int w, int h, int aw, int ah) { } /////////////////////////////////////////////// // // // implementation specific methods // // private void followPath(Shape s, int drawType) { PathIterator points; if (s == null) { return; } if (drawType == FILL) { patternFill(); } if (drawType == STROKE) { if (!(stroke instanceof BasicStroke)) { s = stroke.createStrokedShape(s); followPath(s, FILL); return; } } // if (drawType==STROKE) { // setStrokeDiff(stroke, oldStroke); // oldStroke = stroke; // setStrokePaint(); // } // else if (drawType==FILL) // setFillPaint(); points = s.getPathIterator(IDENTITY); int segments = 0; float[] coords = new float[6]; while (!points.isDone()) { segments++; int segtype = points.currentSegment(coords); switch (segtype) { case PathIterator.SEG_CLOSE: pw.print("h "); break; case PathIterator.SEG_CUBICTO: curveto(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); break; case PathIterator.SEG_LINETO: lineto(coords[0], coords[1]); break; case PathIterator.SEG_MOVETO: moveto(coords[0], coords[1]); break; case PathIterator.SEG_QUADTO: curveto(coords[0], coords[1], coords[2], coords[3]); break; } points.next(); } switch (drawType) { case FILL: if (segments > 0) { if (pattern == null && shading != null) { saveState(); if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("W*"); } else { closeBlock("W"); } pw.println("n"); pw.println(shading + " sh"); restoreState(); return; } if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("f*"); } else { closeBlock("f"); } } break; case STROKE: if (segments > 0) { closeBlock("S"); } break; case CLIP: default: //drawType==CLIP if (segments == 0) { drawRect(0, 0, 0, 0); } if (points.getWindingRule() == PathIterator.WIND_EVEN_ODD) { closeBlock("W*"); } else { closeBlock("W"); } } } /** * @see Graphics2D#getBackground() */ @Override public Color getBackground() { return background; } /** * Returns the Shape of the clipping region As my JDK docs say, this may * break with Java 2D. * * @return Shape of the clipping region */ @Override public Shape getClip() { return null; } /** * Returns the Rectangle that fits the current clipping region * * @return the Rectangle that fits the current clipping region */ @Override public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ @Override public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ @Override public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * * @return the current font. */ @Override public Font getFont() { if (font == null) { setFont(new Font("SansSerif", Font.PLAIN, 12)); } return font; } /** * Returns the FontMetrics for a font. *

* This doesn't work correctly. Perhaps having some way of mapping the base * 14 fonts to our own FontMetrics implementation? * * @param font The java.awt.Font to return the metrics for * @return FontMetrics for a font */ @Override public FontMetrics getFontMetrics(Font font) { Frame dummy = new Frame(); dummy.addNotify(); Image image = dummy.createImage(100, 100); if (image == null) { System.err.println("getFontMetrics: image is null"); } Graphics graphics = image.getGraphics(); return graphics.getFontMetrics(font); } /** * @see Graphics2D#getFontRenderContext() */ @Override public FontRenderContext getFontRenderContext() { boolean antialias = RenderingHints.VALUE_TEXT_ANTIALIAS_ON.equals(getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING)); boolean fractions = RenderingHints.VALUE_FRACTIONALMETRICS_ON.equals(getRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS)); return new FontRenderContext(new AffineTransform(), antialias, fractions); } /** * Returns the associated PDFPage for this graphic * * @return the associated PDFPage for this graphic */ public PDFPage getPage() { return page; } /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Paint getPaint() { return paint; } /** * @param arg0 a key * @return the rendering hint */ @Override public Object getRenderingHint(Key arg0) { return rhints.get(arg0); } /** * @see Graphics2D#getRenderingHints() */ @Override public RenderingHints getRenderingHints() { return rhints; } /** * @see Graphics2D#getStroke() */ @Override public Stroke getStroke() { return stroke; } /** * @see Graphics2D#getTransform() */ @Override public AffineTransform getTransform() { return new AffineTransform(transform); } /** * Returns the PrintWriter handling the underlying stream * * @return the PrintWriter handling the underlying stream */ public RawPrintWriter getWriter() { return pw; } /** * @see Graphics2D#hit(Rectangle, Shape, boolean) */ @Override public boolean hit(Rectangle rect, Shape s, boolean onStroke) { if (onStroke) { s = stroke.createStrokedShape(s); } Area area = new Area(s); if (clip != null) { area.intersect(clip); } return area.intersects(rect.x, rect.y, rect.width, rect.height); } /** * This initialises the stream by saving the current graphics state, and * setting up the default line width (for us). * * It also sets up the instance ready for graphic operations and any * optimisations. * *

* For child instances, the stream is already open, so this should keep * things happy. */ private void init() { PageFormat pf = page.getPageFormat(); // save graphics state (restored by dispose) if (child) { pw.print("q "); } // now initialise the instance //setColor(Color.black); paint = Color.black; // possible: if parent.color is not black, then force black? // must check to see what AWT does? // Original User Space Transform (identity) // Transform from Java Space to PDF Space pTransform = new AffineTransform(); pTransform.translate(0, pf.getHeight()); pTransform.scale(1d, -1d); // Combined Transform User->Java->PDF setNewTranform(new AffineTransform()); // Set the line width setStroke(DEF_STROKE); } /** * This is called by PDFPage when creating a Graphcis instance. * * @param page The PDFPage to draw onto. */ protected void init(PDFPage page) { this.page = page; // We are the parent instance child = false; // Now create a stream to store the graphics in PDFStream stream = new PDFStream(); // To view detail in uncompressed format comment out the next line stream.setDeflate(true); page.getPDFDocument().add(stream); page.add(stream); pw = new RawPrintWriter(stream.getOutputStream()); // initially, we are limited to the page size clipRectangle = page.getImageableArea(); // finally initialise the stream init(); } /** * This method is used internally by create() and by the PDFJob class * * @param page PDFPage to draw into * @param pw PrintWriter to use */ protected void init(PDFPage page, RawPrintWriter pw) { this.page = page; this.pw = pw; // In this case, we didn't create the stream (our parent did) // so child is true (see dispose) child = true; // finally initialise the stream init(); } /** * This adds a line segment to the current path * * @param x coordinate * @param y coordinate */ public void lineto(double x, double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x, y) + "l "); lx = (float) x; ly = (float) y; } /** * This adds a line segment to the current path * * @param x coordinate * @param y coordinate */ public void lineto(int x, int y) { newPath(); if (lx != x && ly != y) { pw.print(cxy(x, y) + "l "); } lx = x; ly = y; } /** * This moves the current drawing point. * * @param x coordinate * @param y coordinate */ public void moveto(double x, double y) { newPath(); // no optimisation here as it may introduce errors on decimal coordinates. pw.print(cxy(x, y) + "m "); lx = (float) x; ly = (float) y; } /** * This moves the current drawing point. * * @param x coordinate * @param y coordinate */ public void moveto(int x, int y) { newPath(); if (lx != x || ly != y) { pw.print(cxy(x, y) + "m "); } lx = x; ly = y; } /** * Functions that draw lines should start by calling this. It starts a new * path unless inStroke is set, in that case it uses the existing path */ void newPath() { if (inText) { closeBlock(); } if (!inStroke) { if (pre_np != null) { pw.print(pre_np); // this is the prefix set by setOrientation() pre_np = null; } pw.print("n "); } inText = false; inStroke = true; // an unlikely coordinate to fool the moveto() optimizer lx = ly = -9999; } /** *

* Functions that draw text should start by calling this. It starts a text * block (accounting for media orientation) unless we are already in a Text * block.

* *

* It also handles if the font has been changed since the current text block * was started, so your function will be current.

* * @param x x coordinate in java space * @param y y coordinate in java space */ void newTextBlock(float x, float y) { // close the current path if there is one if (inStroke) { closeBlock(); } // create the text block if one is not current. If we are, the newFont // condition at the end catches font changes if (!inText) { // This ensures that there is a font available getFont(); pw.print("q BT "); tx = ty = 0; AffineTransform tm = new AffineTransform(pTransform); pw.println("" + df.format(tm.getScaleX()) + " " + "" + df.format(tm.getShearY()) + " " + "" + df.format(tm.getShearX()) + " " + "" + df.format(tm.getScaleY()) + " " + "" + df.format(tm.getTranslateX()) + " " + "" + df.format(tm.getTranslateY()) + " Tm" ); // produce the text matrix for the media // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait // //pw.println("1 0 0 1 0 0 Tm"); // break; // // case PageFormat.LANDSCAPE: // Landscape // pw.println("0 1 -1 0 0 0 Tm"); // rotate // break; // // case 180: // Inverted Portrait // pw.println("1 0 0 -1 0 0 Tm"); // break; // // case PageFormat.REVERSE_LANDSCAPE: // Seascape // pw.println("0 -1 1 0 0 0 Tm"); // rotate // break; // } // move the text cursor by an absolute amount pw.print(txy(x, y) + "Td "); } else { // move the text cursor by a relative amount pw.print(twh(x, y, tx, ty) + "Td "); //pw.print(txy(x,y)+"Td "); } // preserve the coordinates for the next time tx = x; ty = y; if (newFont || !inText) { pw.print(pdffont.getName() + " " + font.getSize() + " Tf "); } // later add colour changes here (if required) inStroke = newFont = false; inText = true; } /** * This is used to add a polygon to the current path. Used by drawPolygon(), * drawPolyline() and fillPolygon() etal * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon * @see #drawPolygon * @see #drawPolyline * @see #fillPolygon */ public void polygon(int[] xp, int[] yp, int np) { // newPath() not needed here as moveto does it ;-) moveto(xp[0], yp[0]); for (int i = 1; i < np; i++) { lineto(xp[i], yp[i]); } } /** * @see Graphics2D#rotate(double) */ @Override public void rotate(double theta) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta); setNewTranform(newTransform); } /** * @see Graphics2D#rotate(double, double, double) */ @Override public void rotate(double theta, double x, double y) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta, x, y); setNewTranform(newTransform); } /** * @see Graphics2D#scale(double, double) */ @Override public void scale(double sx, double sy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.scale(sx, sy); setNewTranform(newTransform); } /** * @see Graphics2D#setBackground(Color) */ @Override public void setBackground(Color color) { background = color; } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void setClip(int x, int y, int w, int h) { /*clipRectangle = new Rectangle(x, y, w, h); closeBlock(); // finish off any existing paths drawRect(x, y, w, h); closeBlock("W n"); // clip to current path*/ setClip(new Rectangle(x, y, w, h)); } /** * As my JDK docs say, this may break with Java 2D. *

* Sets the clipping region to that of a Shape. * * @param s Shape to clip to. */ @Override public void setClip(Shape s) { closeBlock(); if (clip != null) { restoreState(); transform = clipTransform; } if (s == null) { clip = null; return; } clipTransform = transform; clip = new Area(s); clipRectangle = s.getBounds(); saveState(); followPath(s, CLIP); pw.println("n"); //setClip(r.x, r.y, r.width, r.height); } /** * Sets the color for drawing * * @param c Color to use */ @Override public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ @Override public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * * @param f java.awt.Font to set to. */ @Override public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if (font != f) { font = f; pdffont = page.getFont("/Type1", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println("" + lineCap + " J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println("" + lineJoin + " j"); } } /** * This extension allows the width of the drawn line to be set * * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println("" + width + " w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println("" + limit + " M"); } } /** * Sets the paint for drawing * * @param paint Paint to use */ @Override public void setPaint(Paint paint) { this.paint = paint; this.shading = null; this.pattern = null; this.paintTransform = null; if (paint instanceof Color) { Color c = (Color) paint; double r = ((double) c.getRed()) / 255.0; double g = ((double) c.getGreen()) / 255.0; double b = ((double) c.getBlue()) / 255.0; closeBlock(); // This ensures any paths are drawn in the previous if (currentAlpha != c.getAlpha()) { String gsId = "/GSAlpha" + c.getAlpha(); currentAlpha = c.getAlpha(); if (!usedAlphas.contains(c.getAlpha())) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } // colours pw.println("" + r + " " + g + " " + b + " rg " + r + " " + g + " " + b + " RG"); } if (paint instanceof MultipleGradientPaint) { closeBlock(); MultipleGradientPaint grad = (MultipleGradientPaint) paint; /*if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D start = linGrad.getStartPoint(); Point2D end = linGrad.getEndPoint(); double deltaX = end.getX() - start.getX(); double deltaY = end.getY() - start.getY(); Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY); int colorCount = grad.getFractions().length; float fractions2[] = new float[colorCount * 2 - 1]; Color colors2[] = new Color[colorCount * 2 - 1]; for (int i = 0; i < colorCount; i++) { colors2[i] = linGrad.getColors()[i]; fractions2[i] = linGrad.getFractions()[i] / 2; } for (int i = 0; i < colorCount; i++) { colors2[colors2.length - i - 1] = linGrad.getColors()[i]; fractions2[colors2.length - i - 1] = 1f - linGrad.getFractions()[i] / 2; } LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT); grad = linGrad2; }*/ List functions2Refs = new ArrayList<>(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = grad.getColors()[i - 1]; final Color color2 = grad.getColors()[i]; PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getRed()) / 255.0f) + " " + (((float) color1.getGreen()) / 255.0f) + " " + (((float) color1.getBlue()) / 255.0f) + "] /C1 [" + (((float) color2.getRed()) / 255.0f) + " " + (((float) color2.getGreen()) / 255.0f) + " " + (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes()); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 3 /Domain [0 1] /Functions [" + String.join(" ", functions2Refs) + "] ").getBytes()); int lastcols = fgrad.getColors().length - 1; List bounds = new ArrayList<>(); List encode = new ArrayList<>(); for (int i = 1; i < fgrad.getColors().length; i++) { if (i < lastcols) { bounds.add("" + fgrad.getFractions()[i]); } encode.add("0 1"); } os.write(("/Bounds [" + String.join(" ", bounds) + "] ").getBytes()); os.write(("/Encode [" + String.join(" ", encode) + "]\n").getBytes()); writeEnd(os); } }; page.getPDFDocument().add(function3); PDFObject shading = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); if (fgrad instanceof LinearGradientPaint) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; MyDoubleRect coords; //if (linGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { coords = new MyDoubleRect(linGrad.getStartPoint().getX(), linGrad.getStartPoint().getY(), linGrad.getEndPoint().getX(), linGrad.getEndPoint().getY()); /*} else { double deltaX = linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX(); double deltaY = linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY(); double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY); coords = new MyDoubleRect(0, 0, 0, len); }*/ os.write(("/ShadingType 2 /ColorSpace /DeviceRGB " + "/Coords [" + coords.xMin + " " + coords.yMin + " " + coords.xMax + " " + coords.yMax + "] " + "/Domain [0 1] " + "/Function " + function3.getSerialID() + " 0 R /Extend [true true]\n").getBytes()); writeEnd(os); } if (fgrad instanceof RadialGradientPaint) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; os.write(("/ShadingType 3 /ColorSpace /DeviceRGB " + "/Coords [" + radGrad.getFocusPoint().getX() + " " + radGrad.getFocusPoint().getY() + " " + "0 " + radGrad.getCenterPoint().getX() + " " + radGrad.getCenterPoint().getY() + " " + radGrad.getRadius() + "] " + "/Domain [0 1] " + "/Function " + function3.getSerialID() + " 0 R /Extend [true true]\n").getBytes()); writeEnd(os); } } }; page.getPDFDocument().add(shading); shadingCount++; /*if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; double deltaX = linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX(); double deltaY = linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); double len = Math.sqrt(deltaX * deltaX + deltaY * deltaY); AffineTransform m = AffineTransform.getRotateInstance(alfa); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); final int fCurrentShadingCount = shadingCount; PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); double w = Math.abs(linGrad.getEndPoint().getX() - linGrad.getStartPoint().getX()); double h = Math.abs(linGrad.getEndPoint().getY() - linGrad.getStartPoint().getY()); if (w < 1) { w = 1; } if (h < 1) { h = 1; } w = 1; h = len; os.write("/PatternType 1\n".getBytes()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes()); os.write(("/XStep " + w + "\n").getBytes()); os.write(("/YStep " + h + "\n").getBytes()); os.write(("/Resources << " // + "/Pattern << /pin" + shadingCount + " " + pattern.getSerialID() + " 0 R >>" + "/Shading << /Shin" + fCurrentShadingCount + " " + shading.getSerialID() + " 0 R >>" + ">>\n").getBytes()); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/Shin" + shadingCount + " sh").getBytes()); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(innerPattern); page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; return; }*/ //page.addPatternResource("/p" + shadingCount + " " + pattern.getSerialID() + " 0 R"); page.addShadingResource("/Sh" + shadingCount + " " + shading.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } if (paint instanceof TexturePaint) { pattern = "texture"; } } private void initTexturePaint(TexturePaint texturePaint) { BufferedImage img = texturePaint.getImage(); PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); Rectangle2D anchorRect = texturePaint.getAnchorRect(); final double w = anchorRect.getWidth(); final double h = anchorRect.getHeight(); PDFImage image = new PDFImage(img, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R"); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); AffineTransform m = new AffineTransform(); AffineTransform ptt = new AffineTransform(); ptt.concatenate(pTransform); ptt.concatenate(transform); m.concatenate(ptt); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/PatternType 1\n".getBytes()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes()); os.write(("/XStep " + matDf.format(w) + "\n").getBytes()); os.write(("/YStep " + matDf.format(h) + "\n").getBytes()); os.write(("/Resources << ").getBytes()); os.write("/XObject << ".getBytes()); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes()); os.write(" >> ".getBytes()); os.write((">>\n").getBytes()); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); PrintWriter patwriter = new PrintWriter(patOs); AffineTransform transformToSet; transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); patwriter.flush(); page.getPDFDocument().add(innerPattern); shadingCount++; page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; } /** * Not implemented, as this is not supported in the PDF specification. */ @Override public void setPaintMode() { } /** * Sets a rendering hint * * @param arg0 * @param arg1 */ @Override public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ @Override public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ @Override public void setStroke(Stroke s) { this.stroke = s; if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ @Override public void setTransform(AffineTransform t) { setNewTranform(new AffineTransform(t)); } /** * Not implemented, as this is not supported in the PDF specification. * * @param c1 Color to xor with */ @Override public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ @Override public void shear(double shx, double shy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.shear(shx, shy); setNewTranform(newTransform); } /** * @see Graphics2D#transform(AffineTransform) */ @Override public void transform(AffineTransform tx) { AffineTransform newTransform = new AffineTransform(transform); newTransform.concatenate(tx); setNewTranform(newTransform); } /** * @see Graphics2D#translate(double, double) */ @Override public void translate(double tx, double ty) { AffineTransform newTransform = new AffineTransform(transform); newTransform.translate(tx, ty); setNewTranform(newTransform); } /** * @see Graphics#translate(int, int) */ @Override public void translate(int x, int y) { translate((double) x, (double) y); } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x, float y, float tx, float ty) { float nx = x, ny = y; float ntx = tx, nty = ty; nx = (float) (x - tx); ny = (float) (y - ty); return "" + df.format(nx) + " " + df.format(ny) + " "; } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x, float y) { Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); pTransform.transform(ptSrc, ptDst); return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " "; } private void setNewTranform(AffineTransform t) { closeBlock(); AffineTransform newTransform = new AffineTransform(t); AffineTransform transformToSet = new AffineTransform(newTransform); if (transform != null) { AffineTransform aInv = new AffineTransform(transform); try { aInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } AffineTransform pInv = new AffineTransform(pTransform); try { pInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } transformToSet = new AffineTransform(); transformToSet.concatenate(aInv); transformToSet.concatenate(pInv); transformToSet.concatenate(pTransform); transformToSet.concatenate(newTransform); } else { transformToSet.preConcatenate(pTransform); } transform = newTransform; pw.println("" + matDf.format(transformToSet.getScaleX()) + " " + "" + matDf.format(transformToSet.getShearY()) + " " + "" + matDf.format(transformToSet.getShearX()) + " " + "" + matDf.format(transformToSet.getScaleY()) + " " + "" + matDf.format(transformToSet.getTranslateX()) + " " + "" + matDf.format(transformToSet.getTranslateY()) + " cm" ); } private void saveState() { pw.println("q"); } private void restoreState() { pw.println("Q"); } } // end class PDFGraphics \ No newline at end of file