From ae88f5d9b7aeebaeac179be198e91aaba4d33775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Wed, 2 Nov 2022 12:50:24 +0100 Subject: [PATCH] Fixed PDF export - reusing images when used as pattern vs standalone --- CHANGELOG.md | 1 + lib/gnujpdf.jar | Bin 207773 -> 208912 bytes libsrc/gnujpdf/src/gnu/jpdf/PDFDocument.java | 21 +++++++++++++++++++ libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44555e87d..b42253b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ### Fixed - [#1817] PDF export - now storing JPEG images without recompression to PNG - [#1816] PDF export - leaking temporary files when frame has embedded texts +- PDF export - reusing images when used as pattern vs standalone ## [16.0.2] - 2022-11-01 ### Added diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 72ac4a7cf1ec2d65c0e761ef52ededf183d87ec3..d531d8136653c0fdb8563d66256087fe36a6e442 100644 GIT binary patch delta 44934 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 delta 44394 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 usedImages = new WeakHashMap(); + + + public boolean isImageCached(Image image) { + return usedImages.containsKey(image); + } + + public PDFImage getCachedImage(Image image) { + if (!isImageCached(image)) { + return null; + } + return usedImages.get(image); + } + + public void cacheImage(Image image, PDFImage pdfImage) { + usedImages.put(image, pdfImage); + } + /** *

* This page mode indicates that the document should be opened just with the diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java index a2b322f21..2215df23a 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java @@ -1 +1 @@ -/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gnu.jpdf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.LinearGradientPaint; import java.awt.MultipleGradientPaint; import java.awt.Paint; import java.awt.Polygon; import java.awt.RadialGradientPaint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.Shape; import java.awt.Stroke; import java.awt.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.Ellipse2D; 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.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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; import jdk.internal.reflect.Reflection; /** * 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 Set gsBlendModes = new HashSet<>(); private int currentAlpha = 255; private String blendMode; private int shadingCount = 0; private int objId = 0; private boolean usePTransform = true; private static int[] srgbToLinear = new int[256]; private static int[] linearToSrgb = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(i); linearToSrgb[i] = convertLinearRGBtoSRGB(i); } } private static int convertSRGBtoLinearRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.04045f) { output = input / 12.92f; } else { output = (float) Math.pow((input + 0.055) / 1.055, 2.4); } return Math.round(output * 255.0f); } private static int convertLinearRGBtoSRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.0031308) { output = input * 12.92f; } else { output = (1.055f * ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f; } return Math.round(output * 255.0f); } /** * @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); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; byte[] jpegImageData = getImageJpegData(img); if (jpegImageData != null) { image = new PDFImage(jpegImageData, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } // 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"); initAlpha(255); // 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(null/*FIXME!!!*/); drawArc(x, y, w, h, sa, aa); lineto(x + (w >> 1), y + (h >> 1)); if (shadingFill(null)) { 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(null /*FIXME!!!*/); polygon(xp, yp, np); if (shadingFill(null)) { 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(Shape s) { 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, s); paintTransform = transform; } } pw.println("/Pattern cs"); pw.println(pattern + " scn"); } } private boolean shadingFill(Shape s) { if (pattern == null && shading != null) { saveState(); pw.println("W n"); if (paint instanceof MultipleGradientPaint) { if (shading.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint, s); 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(s); } 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, s); 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() { if (clip == null) { return null; } return clip; } /** * 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 uncompressesd 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 = usePTransform ? new AffineTransform(pTransform) : new AffineTransform(); 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(); AffineTransform currentTransform = transform; transform = clipTransform; setTransform(currentTransform); } 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"); } } private void initAlpha(int alpha) { if (currentAlpha != alpha || blendMode != null) { String gsId = "/GSAlpha" + alpha; currentAlpha = alpha; if (!usedAlphas.contains(alpha)) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } } public void setBlendMode(String mode) { if (currentAlpha < 0) { currentAlpha = 255; } String gsName = "/GSBlend" + mode + "Alpha" + currentAlpha; if (!gsBlendModes.contains(gsName)) { page.addExtGStateResource(gsName + " <>"); } pw.println(gsName + " gs"); this.blendMode = mode; } /** * 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 initAlpha(c.getAlpha()); // 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 useFunctionShading(MultipleGradientPaint fgrad) { return ((fgrad instanceof RadialGradientPaint) && fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE); } private Color[] convertColorSpace(Color[] colors, MultipleGradientPaint.ColorSpaceType colorSpaceType) { /*if (colorSpaceType == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { Color[] ret = new Color[colors.length]; for (int i = 0; i < colors.length; i++) { int argb = colors[i].getRGB(); int a = argb >>> 24; int r = srgbToLinear[(argb >> 16) & 0xff]; int g = srgbToLinear[(argb >> 8) & 0xff]; int b = srgbToLinear[(argb) & 0xff]; ret[i] = new Color(r, g, b, a); } return ret; }*/ //return colors; return colors; } private String generateRadialFunctionBody(RadialGradientPaint radGrad, boolean alpha) { double a = ((radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) * (radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) * (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) - radGrad.getRadius() * radGrad.getRadius()); String functionBody = "{\n" + matDf.format(radGrad.getFocusPoint().getX()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + " mul 2 mul\n" //stack size: 3 + matDf.format(radGrad.getFocusPoint().getY()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) + " mul 2 mul\n" + "add\n" //b, stack size: 3 + matDf.format(radGrad.getFocusPoint().getX()) + " 3 index sub\n" + "dup mul\n" + matDf.format(radGrad.getFocusPoint().getY()) + " 3 index sub\n" + "dup mul\n" + "add\n" //c, stack size: 4 + "1 index dup mul 4 " + matDf.format(a) + " mul 2 index mul sub\n" //D, stack size: 4 + "0 index 0 lt\n" + "{\n" + "1\n" + "}" + "{" + "0 index 0 gt\n" + "{\n" + "2 index neg 1 index sqrt add 2 " + matDf.format(a) + " mul div\n" // x1, stack size: 5 + "3 index neg 2 index sqrt sub 2 " + matDf.format(a) + " mul div\n" // x2, stack size: 6 + "0 index 2 index gt{0 index} {1 index} ifelse\n" + "exch pop exch pop\n" //x, stack size 5 + "}" + "{\n" + "2 index neg 2 " + matDf.format(a) + " mul div\n" // x, stack size 5 + "} ifelse\n" + "} ifelse\n" + "exch pop exch pop exch pop exch pop exch pop\n"; //remove index0,1,2,3,4 if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "dup\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { functionBody += "dup 1 gt {pop 1} if\n"; } else { functionBody += "dup 1 gt {dup floor sub} if\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "exch floor 2 mod 1 eq {" + "neg 1 add" + "}\n" + "if\n"; } int num = radGrad.getFractions().length; Color[] rcolors = convertColorSpace(radGrad.getColors(), radGrad.getColorSpace()); for (int i = 0; i < num - 1; i++) { functionBody += "dup " + radGrad.getFractions()[i] + " lt not 1 index " + radGrad.getFractions()[i + 1] + " gt not and\n{\n" + "0 index " + radGrad.getFractions()[i] + " sub " + (radGrad.getFractions()[i + 1] - radGrad.getFractions()[i]) + " div\n"; if (alpha) { functionBody += "0 index " + ((rcolors[i + 1].getAlpha() - rcolors[i].getAlpha()) / 255.0) + " mul " + (rcolors[i].getAlpha() / 255.0) + " add\n"; functionBody += "dup dup\n"; } else { if (radGrad.getColorSpace() == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { functionBody += "0 index " + ((srgbToLinear[rcolors[i + 1].getRed()] - srgbToLinear[rcolors[i].getRed()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getRed()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + ((srgbToLinear[rcolors[i + 1].getGreen()] - srgbToLinear[rcolors[i].getGreen()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getGreen()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + ((srgbToLinear[rcolors[i + 1].getBlue()] - srgbToLinear[rcolors[i].getBlue()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getBlue()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"; } else { functionBody += "0 index " + ((rcolors[i + 1].getRed() - rcolors[i].getRed()) / 255.0) + " mul " + (rcolors[i].getRed() / 255.0) + " add\n" + "1 index " + ((rcolors[i + 1].getGreen() - rcolors[i].getGreen()) / 255.0) + " mul " + (rcolors[i].getGreen() / 255.0) + " add\n" + "2 index " + ((rcolors[i + 1].getBlue() - rcolors[i].getBlue()) / 255.0) + " mul " + (rcolors[i].getBlue() / 255.0) + " add\n"; } } if (i < num - 2) { functionBody += "}\n{\n"; } } functionBody += "}if\n"; for (int i = 0; i < num - 2; i++) { functionBody += "}ifelse\n"; } functionBody += "}\n"; return functionBody; } private void initGradientPaint(MultipleGradientPaint grad, Shape fillShape) { 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<>(); Color[] colors = convertColorSpace(grad.getColors(), grad.getColorSpace()); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = colors[i - 1]; final Color color2 = colors[i]; final MultipleGradientPaint.ColorSpaceType colorSpace = grad.getColorSpace(); if (colorSpace == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { PDFStream function4 = new PDFStream(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1]".getBytes("UTF-8")); writeStream(os); } }; OutputStream f4Os = function4.getOutputStream(); int redDelta = srgbToLinear[color2.getRed()] - srgbToLinear[color1.getRed()]; int greenDelta = srgbToLinear[color2.getGreen()] - srgbToLinear[color1.getGreen()]; int blueDelta = srgbToLinear[color2.getBlue()] - srgbToLinear[color1.getBlue()]; String functionBody = "{" + "0 index " + redDelta + " mul " + srgbToLinear[color1.getRed()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + greenDelta + " mul " + srgbToLinear[color1.getGreen()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + blueDelta + " mul " + srgbToLinear[color1.getBlue()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "}"; try { f4Os.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(function4); functions2Refs.add(function4.getSerialID() + " 0 R"); } else { // 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("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } } List functions2AlphaRefs = new ArrayList<>(); Color[] alphaColors = grad.getColors(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = alphaColors[i - 1]; final Color color2 = alphaColors[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.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + "] /C1 [" + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2AlphaRefs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFGradientFunction3(fgrad, functions2Refs); page.getPDFDocument().add(function3); PDFObject function3Alpha = new PDFGradientFunction3(fgrad, functions2AlphaRefs); page.getPDFDocument().add(function3Alpha); 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; PDFStream radialFunction; PDFStream radialAlphaFunction; if (useFunctionShading(fgrad)) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; radialFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; radialAlphaFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; //PDF reference, page 176 /*double b = 2 * (focalX - x) * (centerX - focalX) + 2 * (focalY - y) * (centerY - focalY); double c = (focalX - x) * (focalX - x) + (focalY - y) * (focalY - y); double D = b * b - 4 * a * c;*/ //(-b + Math.sqrt(D)) / (2 * a) //D = b * b - 4 * a * c; String functionBody = generateRadialFunctionBody(radGrad, false); OutputStream funOs = radialFunction.getOutputStream(); try { funOs.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } String alphaFunctionBody = generateRadialFunctionBody(radGrad, true); funOs = radialAlphaFunction.getOutputStream(); try { funOs.write(alphaFunctionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(radialFunction); page.getPDFDocument().add(radialAlphaFunction); } else { radialFunction = null; radialAlphaFunction = null; } PDFObject shadingObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3, radialFunction); page.getPDFDocument().add(shadingObj); PDFObject shadingAlphaObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3Alpha, radialAlphaFunction); page.getPDFDocument().add(shadingAlphaObj); shadingCount++; final int fCurrentShadingCount = shadingCount; PDFStream alphaObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Group << /CS /DeviceGray /S /Transparency >>\n".getBytes("UTF-8")); os.write("/Type /XObject\n".getBytes("UTF-8")); os.write("/Resources <<\n".getBytes("UTF-8")); os.write("/Shading <<".getBytes("UTF-8")); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(">>\n".getBytes("UTF-8")); //shading os.write(">>\n".getBytes("UTF-8")); //resources os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(alphaObject); String alphaExtGState = "/GradAlpha" + fCurrentShadingCount + " <<" + "/SMask <<" + "/Type /Mask\n" + "/S /Luminosity\n" + "/G " + alphaObject.getSerialID() + " 0 R" + ">>" + ">>"; 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(); if (usePTransform) { 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()); 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("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes("UTF-8")); os.write(("/XStep " + w + "\n").getBytes("UTF-8")); os.write(("/YStep " + h + "\n").getBytes("UTF-8")); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes("UTF-8")); os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes("UTF-8")); patOs.write(("/Shin" + shadingCount + " sh").getBytes("UTF-8")); } 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; } currentAlpha = -1; page.addExtGStateResource(alphaExtGState); pw.println("/GradAlpha" + fCurrentShadingCount + " gs"); page.addShadingResource("/Sh" + shadingCount + " " + shadingObj.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } /** * Hack: * Images, which have method boolean isJpeg() and byte[] getImageData() can be stored as JPEGs * You must implement an Image which has these methods to be properly able to save JPEGS */ private byte[] getImageJpegData(Image img) { byte[] jpegImageData = null; try { Method jpegMethod = img.getClass().getDeclaredMethod("isJpeg"); boolean isJpeg = (boolean) jpegMethod.invoke(img); if (isJpeg) { Method getImageDataMethod = img.getClass().getDeclaredMethod("getImageData"); jpegImageData = (byte[]) getImageDataMethod.invoke(img); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //ignore } return jpegImageData; } private void initTexturePaint(TexturePaint texturePaint) { byte[] jpegImageData = getImageJpegData(texturePaint.getImage()); 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(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { if (jpegImageData != null) { image = new PDFImage(jpegImageData, 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", interpolate); } else { 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", interpolate); } // 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(); if (usePTransform) { 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("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes("UTF-8")); os.write(("/XStep " + matDf.format(w) + "\n").getBytes("UTF-8")); os.write(("/YStep " + matDf.format(h) + "\n").getBytes("UTF-8")); os.write(("/Resources << ").getBytes("UTF-8")); os.write("/XObject << ".getBytes("UTF-8")); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(" >> ".getBytes("UTF-8")); os.write((">>\n").getBytes("UTF-8")); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); 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(); if (usePTransform) { pTransform.transform(ptSrc, ptDst); } return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " "; } private void setNewTranform(AffineTransform t) { closeBlock(); if (true) { //return; } AffineTransform newTransform = new AffineTransform(t); AffineTransform transformToSet = new AffineTransform(newTransform); if (usePTransform) { 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); } } if (clip != null) { AffineTransform inv = new AffineTransform(newTransform); try { inv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } clip = new Area(transform.createTransformedShape(inv.createTransformedShape(clip))); clipRectangle = clip.getBounds(); } 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"); } public void drawXObject(Graphics g) { if (g instanceof PDFGraphics) { int objId = ((PDFGraphics) g).objId; pw.println("/MyObj" + objId + " Do"); } } public Graphics2D createXObject() { final PDFPage newPage = new PDFPage(page.pageFormat) { @Override public void addToProcset(String proc) { page.addToProcset(proc); } }; newPage.pdfDocument = page.pdfDocument; PDFStream retObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Type /XObject\n".getBytes("UTF-8")); newPage.writeResources(os); os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; page.getPDFDocument().add(retObject); OutputStream os = retObject.getOutputStream(); RawPrintWriter pw2 = new RawPrintWriter(os); page.addXObject("/MyObj" + retObject.getSerialID() + " " + retObject.getSerialID() + " 0 R"); PDFGraphics g = new PDFGraphics(); g.usePTransform = false; g.init(newPage, pw2); g.objId = retObject.getSerialID(); //g.setTransform(new AffineTransform()); return g; } } \ 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.Ellipse2D; 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.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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; import jdk.internal.reflect.Reflection; /** * 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); } private Map usedImagesUseMainResources = 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 Set gsBlendModes = new HashSet<>(); private int currentAlpha = 255; private String blendMode; private int shadingCount = 0; private int objId = 0; private boolean usePTransform = true; private static int[] srgbToLinear = new int[256]; private static int[] linearToSrgb = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(i); linearToSrgb[i] = convertLinearRGBtoSRGB(i); } } private static int convertSRGBtoLinearRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.04045f) { output = input / 12.92f; } else { output = (float) Math.pow((input + 0.055) / 1.055, 2.4); } return Math.round(output * 255.0f); } private static int convertLinearRGBtoSRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.0031308) { output = input * 12.92f; } else { output = (1.055f * ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f; } return Math.round(output * 255.0f); } /** * @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 (page.getPDFDocument().isImageCached(img)) { image = page.getPDFDocument().getCachedImage(img); //it was previously only part of pattern if (!usedImagesUseMainResources.containsKey(img) || !usedImagesUseMainResources.get(img)) { page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); usedImagesUseMainResources.put(img, true); } } else { PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; //TODO: We should propably cache different images for interpolate vs no interpolate, // But for JPEXS FFDec it's enough I think. // I tried smooth/nonsmooth bitmap in Flash Pro CS6 and it always produced two separate imagetags byte[] jpegImageData = getImageJpegData(img); if (jpegImageData != null) { image = new PDFImage(jpegImageData, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); page.getPDFDocument().cacheImage(img, image); page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); usedImagesUseMainResources.put(img, true); } initAlpha(255); // 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(null/*FIXME!!!*/); drawArc(x, y, w, h, sa, aa); lineto(x + (w >> 1), y + (h >> 1)); if (shadingFill(null)) { 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(null /*FIXME!!!*/); polygon(xp, yp, np); if (shadingFill(null)) { 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(Shape s) { 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, s); paintTransform = transform; } } pw.println("/Pattern cs"); pw.println(pattern + " scn"); } } private boolean shadingFill(Shape s) { if (pattern == null && shading != null) { saveState(); pw.println("W n"); if (paint instanceof MultipleGradientPaint) { if (shading.equals("gradient") || !paintTransform.equals(transform)) { initGradientPaint((MultipleGradientPaint) paint, s); 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(s); } 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, s); 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() { if (clip == null) { return null; } return clip; } /** * 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 uncompressesd 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 = usePTransform ? new AffineTransform(pTransform) : new AffineTransform(); 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(); AffineTransform currentTransform = transform; transform = clipTransform; setTransform(currentTransform); } 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"); } } private void initAlpha(int alpha) { if (currentAlpha != alpha || blendMode != null) { String gsId = "/GSAlpha" + alpha; currentAlpha = alpha; if (!usedAlphas.contains(alpha)) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } } public void setBlendMode(String mode) { if (currentAlpha < 0) { currentAlpha = 255; } String gsName = "/GSBlend" + mode + "Alpha" + currentAlpha; if (!gsBlendModes.contains(gsName)) { page.addExtGStateResource(gsName + " <>"); } pw.println(gsName + " gs"); this.blendMode = mode; } /** * 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 initAlpha(c.getAlpha()); // 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 useFunctionShading(MultipleGradientPaint fgrad) { return ((fgrad instanceof RadialGradientPaint) && fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE); } private Color[] convertColorSpace(Color[] colors, MultipleGradientPaint.ColorSpaceType colorSpaceType) { /*if (colorSpaceType == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { Color[] ret = new Color[colors.length]; for (int i = 0; i < colors.length; i++) { int argb = colors[i].getRGB(); int a = argb >>> 24; int r = srgbToLinear[(argb >> 16) & 0xff]; int g = srgbToLinear[(argb >> 8) & 0xff]; int b = srgbToLinear[(argb) & 0xff]; ret[i] = new Color(r, g, b, a); } return ret; }*/ //return colors; return colors; } private String generateRadialFunctionBody(RadialGradientPaint radGrad, boolean alpha) { double a = ((radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) * (radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) * (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) - radGrad.getRadius() * radGrad.getRadius()); String functionBody = "{\n" + matDf.format(radGrad.getFocusPoint().getX()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + " mul 2 mul\n" //stack size: 3 + matDf.format(radGrad.getFocusPoint().getY()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) + " mul 2 mul\n" + "add\n" //b, stack size: 3 + matDf.format(radGrad.getFocusPoint().getX()) + " 3 index sub\n" + "dup mul\n" + matDf.format(radGrad.getFocusPoint().getY()) + " 3 index sub\n" + "dup mul\n" + "add\n" //c, stack size: 4 + "1 index dup mul 4 " + matDf.format(a) + " mul 2 index mul sub\n" //D, stack size: 4 + "0 index 0 lt\n" + "{\n" + "1\n" + "}" + "{" + "0 index 0 gt\n" + "{\n" + "2 index neg 1 index sqrt add 2 " + matDf.format(a) + " mul div\n" // x1, stack size: 5 + "3 index neg 2 index sqrt sub 2 " + matDf.format(a) + " mul div\n" // x2, stack size: 6 + "0 index 2 index gt{0 index} {1 index} ifelse\n" + "exch pop exch pop\n" //x, stack size 5 + "}" + "{\n" + "2 index neg 2 " + matDf.format(a) + " mul div\n" // x, stack size 5 + "} ifelse\n" + "} ifelse\n" + "exch pop exch pop exch pop exch pop exch pop\n"; //remove index0,1,2,3,4 if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "dup\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { functionBody += "dup 1 gt {pop 1} if\n"; } else { functionBody += "dup 1 gt {dup floor sub} if\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "exch floor 2 mod 1 eq {" + "neg 1 add" + "}\n" + "if\n"; } int num = radGrad.getFractions().length; Color[] rcolors = convertColorSpace(radGrad.getColors(), radGrad.getColorSpace()); for (int i = 0; i < num - 1; i++) { functionBody += "dup " + radGrad.getFractions()[i] + " lt not 1 index " + radGrad.getFractions()[i + 1] + " gt not and\n{\n" + "0 index " + radGrad.getFractions()[i] + " sub " + (radGrad.getFractions()[i + 1] - radGrad.getFractions()[i]) + " div\n"; if (alpha) { functionBody += "0 index " + ((rcolors[i + 1].getAlpha() - rcolors[i].getAlpha()) / 255.0) + " mul " + (rcolors[i].getAlpha() / 255.0) + " add\n"; functionBody += "dup dup\n"; } else { if (radGrad.getColorSpace() == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { functionBody += "0 index " + ((srgbToLinear[rcolors[i + 1].getRed()] - srgbToLinear[rcolors[i].getRed()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getRed()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + ((srgbToLinear[rcolors[i + 1].getGreen()] - srgbToLinear[rcolors[i].getGreen()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getGreen()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + ((srgbToLinear[rcolors[i + 1].getBlue()] - srgbToLinear[rcolors[i].getBlue()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getBlue()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"; } else { functionBody += "0 index " + ((rcolors[i + 1].getRed() - rcolors[i].getRed()) / 255.0) + " mul " + (rcolors[i].getRed() / 255.0) + " add\n" + "1 index " + ((rcolors[i + 1].getGreen() - rcolors[i].getGreen()) / 255.0) + " mul " + (rcolors[i].getGreen() / 255.0) + " add\n" + "2 index " + ((rcolors[i + 1].getBlue() - rcolors[i].getBlue()) / 255.0) + " mul " + (rcolors[i].getBlue() / 255.0) + " add\n"; } } if (i < num - 2) { functionBody += "}\n{\n"; } } functionBody += "}if\n"; for (int i = 0; i < num - 2; i++) { functionBody += "}ifelse\n"; } functionBody += "}\n"; return functionBody; } private void initGradientPaint(MultipleGradientPaint grad, Shape fillShape) { 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<>(); Color[] colors = convertColorSpace(grad.getColors(), grad.getColorSpace()); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = colors[i - 1]; final Color color2 = colors[i]; final MultipleGradientPaint.ColorSpaceType colorSpace = grad.getColorSpace(); if (colorSpace == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { PDFStream function4 = new PDFStream(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1]".getBytes("UTF-8")); writeStream(os); } }; OutputStream f4Os = function4.getOutputStream(); int redDelta = srgbToLinear[color2.getRed()] - srgbToLinear[color1.getRed()]; int greenDelta = srgbToLinear[color2.getGreen()] - srgbToLinear[color1.getGreen()]; int blueDelta = srgbToLinear[color2.getBlue()] - srgbToLinear[color1.getBlue()]; String functionBody = "{" + "0 index " + redDelta + " mul " + srgbToLinear[color1.getRed()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + greenDelta + " mul " + srgbToLinear[color1.getGreen()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + blueDelta + " mul " + srgbToLinear[color1.getBlue()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "}"; try { f4Os.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(function4); functions2Refs.add(function4.getSerialID() + " 0 R"); } else { // 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("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } } List functions2AlphaRefs = new ArrayList<>(); Color[] alphaColors = grad.getColors(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = alphaColors[i - 1]; final Color color2 = alphaColors[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.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + "] /C1 [" + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2AlphaRefs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFGradientFunction3(fgrad, functions2Refs); page.getPDFDocument().add(function3); PDFObject function3Alpha = new PDFGradientFunction3(fgrad, functions2AlphaRefs); page.getPDFDocument().add(function3Alpha); 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; PDFStream radialFunction; PDFStream radialAlphaFunction; if (useFunctionShading(fgrad)) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; radialFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; radialAlphaFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; //PDF reference, page 176 /*double b = 2 * (focalX - x) * (centerX - focalX) + 2 * (focalY - y) * (centerY - focalY); double c = (focalX - x) * (focalX - x) + (focalY - y) * (focalY - y); double D = b * b - 4 * a * c;*/ //(-b + Math.sqrt(D)) / (2 * a) //D = b * b - 4 * a * c; String functionBody = generateRadialFunctionBody(radGrad, false); OutputStream funOs = radialFunction.getOutputStream(); try { funOs.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } String alphaFunctionBody = generateRadialFunctionBody(radGrad, true); funOs = radialAlphaFunction.getOutputStream(); try { funOs.write(alphaFunctionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(radialFunction); page.getPDFDocument().add(radialAlphaFunction); } else { radialFunction = null; radialAlphaFunction = null; } PDFObject shadingObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3, radialFunction); page.getPDFDocument().add(shadingObj); PDFObject shadingAlphaObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3Alpha, radialAlphaFunction); page.getPDFDocument().add(shadingAlphaObj); shadingCount++; final int fCurrentShadingCount = shadingCount; PDFStream alphaObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Group << /CS /DeviceGray /S /Transparency >>\n".getBytes("UTF-8")); os.write("/Type /XObject\n".getBytes("UTF-8")); os.write("/Resources <<\n".getBytes("UTF-8")); os.write("/Shading <<".getBytes("UTF-8")); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(">>\n".getBytes("UTF-8")); //shading os.write(">>\n".getBytes("UTF-8")); //resources os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(alphaObject); String alphaExtGState = "/GradAlpha" + fCurrentShadingCount + " <<" + "/SMask <<" + "/Type /Mask\n" + "/S /Luminosity\n" + "/G " + alphaObject.getSerialID() + " 0 R" + ">>" + ">>"; 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(); if (usePTransform) { 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()); 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("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes("UTF-8")); os.write(("/XStep " + w + "\n").getBytes("UTF-8")); os.write(("/YStep " + h + "\n").getBytes("UTF-8")); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes("UTF-8")); os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes("UTF-8")); patOs.write(("/Shin" + shadingCount + " sh").getBytes("UTF-8")); } 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; } currentAlpha = -1; page.addExtGStateResource(alphaExtGState); pw.println("/GradAlpha" + fCurrentShadingCount + " gs"); page.addShadingResource("/Sh" + shadingCount + " " + shadingObj.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } /** * Hack: * Images, which have method boolean isJpeg() and byte[] getImageData() can be stored as JPEGs * You must implement an Image which has these methods to be properly able to save JPEGS */ private byte[] getImageJpegData(Image img) { byte[] jpegImageData = null; try { Method jpegMethod = img.getClass().getDeclaredMethod("isJpeg"); boolean isJpeg = (boolean) jpegMethod.invoke(img); if (isJpeg) { Method getImageDataMethod = img.getClass().getDeclaredMethod("getImageData"); jpegImageData = (byte[]) getImageDataMethod.invoke(img); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //ignore } return jpegImageData; } private void initTexturePaint(TexturePaint texturePaint) { byte[] jpegImageData = getImageJpegData(texturePaint.getImage()); 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(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (page.getPDFDocument().isImageCached(img)) { image = page.getPDFDocument().getCachedImage(img); } else { if (jpegImageData != null) { image = new PDFImage(jpegImageData, 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", interpolate); } else { 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", interpolate); } // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); page.getPDFDocument().cacheImage(img, image); usedImagesUseMainResources.put(img, false); } AffineTransform m = new AffineTransform(); AffineTransform ptt = new AffineTransform(); if (usePTransform) { 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("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes("UTF-8")); os.write(("/XStep " + matDf.format(w) + "\n").getBytes("UTF-8")); os.write(("/YStep " + matDf.format(h) + "\n").getBytes("UTF-8")); os.write(("/Resources << ").getBytes("UTF-8")); os.write("/XObject << ".getBytes("UTF-8")); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(" >> ".getBytes("UTF-8")); os.write((">>\n").getBytes("UTF-8")); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); 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(); if (usePTransform) { pTransform.transform(ptSrc, ptDst); } return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " "; } private void setNewTranform(AffineTransform t) { closeBlock(); if (true) { //return; } AffineTransform newTransform = new AffineTransform(t); AffineTransform transformToSet = new AffineTransform(newTransform); if (usePTransform) { 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); } } if (clip != null) { AffineTransform inv = new AffineTransform(newTransform); try { inv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } clip = new Area(transform.createTransformedShape(inv.createTransformedShape(clip))); clipRectangle = clip.getBounds(); } 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"); } public void drawXObject(Graphics g) { if (g instanceof PDFGraphics) { int objId = ((PDFGraphics) g).objId; pw.println("/MyObj" + objId + " Do"); } } public Graphics2D createXObject() { final PDFPage newPage = new PDFPage(page.pageFormat) { @Override public void addToProcset(String proc) { page.addToProcset(proc); } }; newPage.pdfDocument = page.pdfDocument; PDFStream retObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Type /XObject\n".getBytes("UTF-8")); newPage.writeResources(os); os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; page.getPDFDocument().add(retObject); OutputStream os = retObject.getOutputStream(); RawPrintWriter pw2 = new RawPrintWriter(os); page.addXObject("/MyObj" + retObject.getSerialID() + " " + retObject.getSerialID() + " 0 R"); PDFGraphics g = new PDFGraphics(); g.usePTransform = false; g.init(newPage, pw2); g.objId = retObject.getSerialID(); //g.setTransform(new AffineTransform()); return g; } } \ No newline at end of file