From 5a4c9f34cc31fb35bc541043e95e08b8056b338e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 21 Mar 2021 15:12:35 +0100 Subject: [PATCH] gnujpdf gradient transparency --- lib/gnujpdf.jar | Bin 188836 -> 193917 bytes .../src/gnu/jpdf/PDFGradientFunction3.java | 43 ++++++++ libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- .../src/gnu/jpdf/PdfGradientShading.java | 95 ++++++++++++++++++ 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 libsrc/gnujpdf/src/gnu/jpdf/PDFGradientFunction3.java create mode 100644 libsrc/gnujpdf/src/gnu/jpdf/PdfGradientShading.java diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 57263b0e33275289370c67e9a6608c23b540c5f4..5da33010dafe2e7205ac1cdad5d96f090306b782 100644 GIT binary patch delta 41610 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 z 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 qM0rda5@1;19Zd^h`n|k1P6@;Q9{?_gJI= diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFGradientFunction3.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFGradientFunction3.java new file mode 100644 index 000000000..78061b0f5 --- /dev/null +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFGradientFunction3.java @@ -0,0 +1,43 @@ +package gnu.jpdf; + +import java.awt.MultipleGradientPaint; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class PDFGradientFunction3 extends PDFObject { + + private final MultipleGradientPaint fgrad; + + private final List functions2Refs; + + public PDFGradientFunction3(MultipleGradientPaint fgrad, List functions2Refs) { + super(null); + this.fgrad = fgrad; + this.functions2Refs = functions2Refs; + } + + @Override + public void write(OutputStream os) throws IOException { + writeStart(os); + os.write(("/FunctionType 3 /Domain [0 1] /Functions [" + String.join(" ", functions2Refs) + "] ").getBytes()); + int lastcols = fgrad.getColors().length - 1; + List bounds = new ArrayList<>(); + List encode = new ArrayList<>(); + for (int i = 1; i < fgrad.getColors().length; i++) { + if (i < lastcols) { + bounds.add("" + fgrad.getFractions()[i]); + } + encode.add("0 1"); + } + os.write(("/Bounds [" + String.join(" ", bounds) + "] ").getBytes()); + os.write(("/Encode [" + String.join(" ", encode) + "]\n").getBytes()); + writeEnd(os); + } + +} diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java index 4aaf7eea4..6710eb786 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.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; /** * This class is our implementation of AWT's Graphics class. It provides a Java * standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */ public class PDFGraphics extends Graphics2D implements Serializable { /** * One degree in radians */ private static final double degrees_to_radians = Math.PI / 180.0; private static final int FILL = 1; private static final int STROKE = 2; private static final int CLIP = 3; private static final AffineTransform IDENTITY = new AffineTransform(); private static final Stroke DEF_STROKE = new BasicStroke(); /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.pdf. * The formatting was changed a little bit. * This used to subclass an abstract class in a different package with * the same name (confusing). Now it's one concrete class. * drawImage() was implemented * It is still licensed under the LGPL. */ // Implementation notes: // // Pages 333-335 of the PDF Reference Manual // // Unless absolutely required, use the moveto, lineto and rectangle // operators to perform those actions. // They contain some extra optimizations // which will reduce the output size by up to half in some cases. // // About fill operators: For correct operation, any fill operation should // start with closeBlock(), which will ensure any previous path is completed, // otherwise you may find the fill will include previous items private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH)); private static final DecimalFormat matDf = new DecimalFormat("0", new DecimalFormatSymbols(Locale.ENGLISH)); static { matDf.setMaximumFractionDigits(340); } //JPEXS: cache for already used images private static Map usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * * @see #create */ private boolean child; private Area clip; private AffineTransform clipTransform; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: The last known moveto/lineto x coordinate * * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: The last known moveto/lineto y coordinate * * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; private AffineTransform paintTransform; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: This is written to the stream when the newPath() * is called. np then clears this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties private AffineTransform transform; /** * Part of the optimizer: The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering private String shading = null; private String pattern = null; private Set usedAlphas = new HashSet<>(); private int currentAlpha = 255; private int shadingCount = 0; private static int[] srgbToLinear = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(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); } /** * @see Graphics2D#addRenderingHints(Map) */ @Override public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc, double ayc, double width, double height, double ang1, double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1 % 360.0) * degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width * cos0; y0 = ayc + height * sin0; // NB: !clockwise here as Java Space is inverted to User Space if (!clockwise) { // Quadrant reduction while (ang1 < ang2) { ang2 -= 360.0; } while ((adiff = ang2 - ang1) < -90.0) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while (ang2 < ang1) { ang2 += 360.0; } while ((adiff = ang2 - ang1) > 90.0) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width * sin0; double yt = y0 + trad * height * cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path Important: We * write directly to the stream here, because this method operates in User * space, rather than Java space. * * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w, double h, double x0, double y0, double x3, double y3, double xt, double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx * dx + dy * dy; double w2 = w * w, h2 = h * h; double r2 = w2 + h2; double fw = 0.0, fh = 0.0; if (dist < (r2 * 1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0; } // The path must have a starting point if (first) { moveto(x0, y0); } double x = x0 + ((xt - x0) * fw); double y = y0 + ((yt - y0) * fh); x0 = x3 + ((xt - x3) * fw); y0 = y3 + ((yt - y3) * fh); // Finally the actual curve. curveto(x, y, x0, y0, x3, y3); } /** * This simply draws a White Rectangle to clear the area * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clearRect(int x, int y, int w, int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x, y, w, h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ @Override public void clip(Shape s) { if (s == null) { setClip(null); return; } Area newClip; if (clip == null) { newClip = new Area(s); } else { newClip = (Area) clip.clone(); newClip.intersect(new Area(s)); } setClip(newClip); } /** * This extra method allows PDF users to clip to a Polygon. * *

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

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

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

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

* Draws an oval

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

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

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

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

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

* Not implemented

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ @Override public void fill3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Fills an arc, joining the start and end coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ @Override public void fillArc(int x, int y, int w, int h, int sa, int aa) { // here we fool the optimizer. We force any open path to be closed, // then draw the arc. Finally, as the optimizer hasn't stroke'd the // path, we close and fill it, and mark the Stroke as closed. // // Note: The lineto to the centre of the object is required, because // the fill only fills the arc. Skipping this includes an extra // chord, which isn't correct. Peter May 31 2000 closeBlock(); patternFill(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() { return null; } /** * Returns the Rectangle that fits the current clipping region * * @return the Rectangle that fits the current clipping region */ @Override public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ @Override public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ @Override public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * * @return the current font. */ @Override public Font getFont() { if (font == null) { setFont(new Font("SansSerif", Font.PLAIN, 12)); } return font; } /** * Returns the FontMetrics for a font. *

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

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

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

* *

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

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

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

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

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

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

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

* Draws an oval

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

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

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

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

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

* Not implemented

* * @param x an int value * @param y an int value * @param width an int value * @param height an int value * @param raised a boolean value */ @Override public void fill3DRect(int x, int y, int width, int height, boolean raised) { // Not implemented } /** * Fills an arc, joining the start and end coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height * @param sa Start angle * @param aa End angle */ @Override public void fillArc(int x, int y, int w, int h, int sa, int aa) { // here we fool the optimizer. We force any open path to be closed, // then draw the arc. Finally, as the optimizer hasn't stroke'd the // path, we close and fill it, and mark the Stroke as closed. // // Note: The lineto to the centre of the object is required, because // the fill only fills the arc. Skipping this includes an extra // chord, which isn't correct. Peter May 31 2000 closeBlock(); patternFill(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() { return null; } /** * Returns the Rectangle that fits the current clipping region * * @return the Rectangle that fits the current clipping region */ @Override public Rectangle getClipBounds() { return clipRectangle; } //============ Color operations ======================= /** * Returns the current pen Colour * * @return the current pen Colour */ @Override public Color getColor() { return (paint instanceof Color) ? (Color) paint : Color.black; } /** * @see Graphics2D#getComposite() */ @Override public Composite getComposite() { return composite; } /** * @see Graphics2D#getDeviceConfiguration() */ @Override public GraphicsConfiguration getDeviceConfiguration() { return dg2.getDeviceConfiguration(); } /** * Return's the current font. * * @return the current font. */ @Override public Font getFont() { if (font == null) { setFont(new Font("SansSerif", Font.PLAIN, 12)); } return font; } /** * Returns the FontMetrics for a font. *

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

* For child instances, the stream is already open, so this should keep * things happy. */ private void init() { PageFormat pf = page.getPageFormat(); // save graphics state (restored by dispose) if (child) { pw.print("q "); } // now initialise the instance //setColor(Color.black); paint = Color.black; // possible: if parent.color is not black, then force black? // must check to see what AWT does? // Original User Space Transform (identity) // Transform from Java Space to PDF Space pTransform = new AffineTransform(); pTransform.translate(0, pf.getHeight()); pTransform.scale(1d, -1d); // Combined Transform User->Java->PDF setNewTranform(new AffineTransform()); // Set the line width setStroke(DEF_STROKE); } /** * This is called by PDFPage when creating a Graphcis instance. * * @param page The PDFPage to draw onto. */ protected void init(PDFPage page) { this.page = page; // We are the parent instance child = false; // Now create a stream to store the graphics in PDFStream stream = new PDFStream(); // To view detail in 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 = new AffineTransform(pTransform); pw.println("" + df.format(tm.getScaleX()) + " " + "" + df.format(tm.getShearY()) + " " + "" + df.format(tm.getShearX()) + " " + "" + df.format(tm.getScaleY()) + " " + "" + df.format(tm.getTranslateX()) + " " + "" + df.format(tm.getTranslateY()) + " Tm" ); // produce the text matrix for the media // switch(mediaRot) { // case PageFormat.PORTRAIT: // Portrait // //pw.println("1 0 0 1 0 0 Tm"); // break; // // case PageFormat.LANDSCAPE: // Landscape // pw.println("0 1 -1 0 0 0 Tm"); // rotate // break; // // case 180: // Inverted Portrait // pw.println("1 0 0 -1 0 0 Tm"); // break; // // case PageFormat.REVERSE_LANDSCAPE: // Seascape // pw.println("0 -1 1 0 0 0 Tm"); // rotate // break; // } // move the text cursor by an absolute amount pw.print(txy(x, y) + "Td "); } else { // move the text cursor by a relative amount pw.print(twh(x, y, tx, ty) + "Td "); //pw.print(txy(x,y)+"Td "); } // preserve the coordinates for the next time tx = x; ty = y; if (newFont || !inText) { pw.print(pdffont.getName() + " " + font.getSize() + " Tf "); } // later add colour changes here (if required) inStroke = newFont = false; inText = true; } /** * This is used to add a polygon to the current path. Used by drawPolygon(), * drawPolyline() and fillPolygon() etal * * @param xp Array of x coordinates * @param yp Array of y coordinates * @param np number of points in polygon * @see #drawPolygon * @see #drawPolyline * @see #fillPolygon */ public void polygon(int[] xp, int[] yp, int np) { // newPath() not needed here as moveto does it ;-) moveto(xp[0], yp[0]); for (int i = 1; i < np; i++) { lineto(xp[i], yp[i]); } } /** * @see Graphics2D#rotate(double) */ @Override public void rotate(double theta) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta); setNewTranform(newTransform); } /** * @see Graphics2D#rotate(double, double, double) */ @Override public void rotate(double theta, double x, double y) { AffineTransform newTransform = new AffineTransform(transform); newTransform.rotate(theta, x, y); setNewTranform(newTransform); } /** * @see Graphics2D#scale(double, double) */ @Override public void scale(double sx, double sy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.scale(sx, sy); setNewTranform(newTransform); } /** * @see Graphics2D#setBackground(Color) */ @Override public void setBackground(Color color) { background = color; } /** * Clips to a set of coordinates * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void setClip(int x, int y, int w, int h) { /*clipRectangle = new Rectangle(x, y, w, h); closeBlock(); // finish off any existing paths drawRect(x, y, w, h); closeBlock("W n"); // clip to current path*/ setClip(new Rectangle(x, y, w, h)); } /** * As my JDK docs say, this may break with Java 2D. *

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