From 127427d451fb160e2503664b3f3c8e5697b87599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Thu, 18 Mar 2021 21:55:11 +0100 Subject: [PATCH] gnujpdf - LinearGradientPaint reflect, repeat cycle mode --- lib/gnujpdf.jar | Bin 199353 -> 178689 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 b6bb8011f01e6ab1c59cba6030214f4f4609d702..a91780b9a5ebe97725195974b83f4e94c669596a 100644 GIT binary patch delta 29648 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 delta 47787 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�SnflPH&*X&C9@`OWes&-N@B>1xKG)C6q(L@i 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 z

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 Q2?D}x%q8u 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 +/* * $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; } } if (paint instanceof MultipleGradientPaint) { if (pattern.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint); paintTransform = transform; } } pw.println("/Pattern cs"); pw.println(pattern + " scn"); } } private boolean shadingFill() { if (pattern == null && shading != null) { saveState(); pw.println("W n"); if (paint instanceof MultipleGradientPaint) { if (shading.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint); paintTransform = transform; } } 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"); if (paint instanceof MultipleGradientPaint) { if (shading.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint); paintTransform = transform; } } 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(); if ((paint instanceof RadialGradientPaint) || ((paint instanceof LinearGradientPaint) && (((LinearGradientPaint) paint).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE))) { shading = "gradient"; } else { pattern = "gradient"; } } if (paint instanceof TexturePaint) { closeBlock(); pattern = "texture"; } } /*private boolean isPaintShading(Paint p) { return (p instanceof RadialGradientPaint) || ((p instanceof LinearGradientPaint) && (((LinearGradientPaint) p).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE)); }*/ private void initGradientPaint(MultipleGradientPaint grad) { 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]; float fractionsrev[] = new float[linGrad.getFractions().length]; Color colorsrev[] = new Color[linGrad.getColors().length]; for (int i = 0; i < fractionsrev.length; i++) { colorsrev[i] = linGrad.getColors()[i]; fractionsrev[i] = linGrad.getFractions()[i]; } for (int i = 0; i < colorCount; i++) { colors2[i] = colorsrev[i]; fractions2[i] = fractionsrev[i] / 2; } for (int i = 0; i < colorCount; i++) { colors2[colors2.length - i - 1] = colorsrev[i]; fractions2[colors2.length - i - 1] = 1f - fractionsrev[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); double glen = 0; double divisor = 1; double maxlen = 256; if ((fgrad instanceof LinearGradientPaint) && (fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE)) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; Point2D startPoint = new Point2D.Double(); Point2D endPoint = new Point2D.Double(); startPoint = linGrad.getStartPoint(); endPoint = linGrad.getEndPoint(); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); glen = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (glen > maxlen) { divisor = glen / maxlen; glen = maxlen; } } final double flen = glen; 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) { Point2D startPointTrans = new Point2D.Double(); Point2D endPointTrans = new Point2D.Double(); startPointTrans = linGrad.getStartPoint(); endPointTrans = linGrad.getEndPoint(); coords = new MyDoubleRect(startPointTrans.getX(), startPointTrans.getY(), endPointTrans.getX(), endPointTrans.getY()); } else { coords = new MyDoubleRect(0, 0, 0, flen); } os.write(("/ShadingType 2 /ColorSpace /DeviceRGB " + "/Coords [" + matDf.format(coords.xMin) + " " + matDf.format(coords.yMin) + " " + matDf.format(coords.xMax) + " " + matDf.format(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; Point2D startPoint = linGrad.getStartPoint(); Point2D endPoint = linGrad.getEndPoint(); Point2D startPointTrans = new Point2D.Double(); Point2D endPointTrans = new Point2D.Double(); transform.transform(linGrad.getStartPoint(), startPointTrans); transform.transform(linGrad.getEndPoint(), endPointTrans); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); AffineTransform m = new AffineTransform(); m.concatenate(pTransform); m.concatenate(transform); m.concatenate(AffineTransform.getTranslateInstance(startPoint.getX(), endPoint.getY())); m.concatenate(AffineTransform.getScaleInstance(divisor, divisor)); m.concatenate(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; double h; w = 1; h = flen; 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 << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shading.getSerialID() + " 0 R >>" + ">>\n").getBytes()); 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.addShadingResource("/Sh" + shadingCount + " " + shading.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } 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