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

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (page.getPDFDocument().isImageCached(img, interpolate)) { image = page.getPDFDocument().getCachedImage(img, interpolate); //it was previously only part of pattern if (!usedImagesUseMainResources.contains(new ImageInterpolate(img, interpolate))) { page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); usedImagesUseMainResources.add(new ImageInterpolate(img, interpolate)); } } else { byte[] jpegImageData = getImageJpegData(img); byte[] alphaChannel = null; if (jpegImageData != null) { alphaChannel = getImageAlphaChannel(img); } PDFMask mask = alphaChannel == null ? new PDFMask(img) : new PDFMask(alphaChannel, img.getWidth(null), img.getHeight(null)); page.getPDFDocument().add(mask); //TODO: We should propably cache different images for interpolate vs no interpolate, // But for JPEXS FFDec it's enough I think. // I tried smooth/nonsmooth bitmap in Flash Pro CS6 and it always produced two separate imagetags if (jpegImageData != null) { image = new PDFImage(jpegImageData, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); page.getPDFDocument().cacheImage(img, image); page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); usedImagesUseMainResources.add(new ImageInterpolate(img, interpolate)); } initAlpha(255); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h); AffineTransform transformToSet = newTransform; pw.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

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

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

* Draws an oval

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

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

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

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

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

* Not implemented

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

* Draws a filled oval

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

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

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

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

* *

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

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

* Sets the clipping region to that of a Shape. * * @param s Shape to clip to. */ @Override public void setClip(Shape s) { closeBlock(); if (clip != null) { restoreState(); AffineTransform currentTransform = transform; transform = clipTransform; setTransform(currentTransform); } if (s == null) { clip = null; return; } clipTransform = transform; clip = new Area(s); clipRectangle = s.getBounds(); saveState(); followPath(s, CLIP); pw.println("n"); //setClip(r.x, r.y, r.width, r.height); } /** * Sets the color for drawing * * @param c Color to use */ @Override public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ @Override public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * * @param f java.awt.Font to set to. */ @Override public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if (font != f) { font = f; pdffont = page.getFont("/Type1", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println("" + lineCap + " J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println("" + lineJoin + " j"); } } /** * This extension allows the width of the drawn line to be set * * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println("" + width + " w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println("" + limit + " M"); } } private void initAlpha(int alpha) { if (currentAlpha != alpha || blendMode != null) { String gsId = "/GSAlpha" + alpha; currentAlpha = alpha; if (!usedAlphas.contains(alpha)) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } } public void setBlendMode(String mode) { if (currentAlpha < 0) { currentAlpha = 255; } String gsName = "/GSBlend" + mode + "Alpha" + currentAlpha; if (!gsBlendModes.contains(gsName)) { page.addExtGStateResource(gsName + " <>"); } pw.println(gsName + " gs"); this.blendMode = mode; } /** * Sets the paint for drawing * * @param paint Paint to use */ @Override public void setPaint(Paint paint) { this.paint = paint; this.shading = null; this.pattern = null; this.paintTransform = null; if (paint instanceof Color) { Color c = (Color) paint; double r = ((double) c.getRed()) / 255.0; double g = ((double) c.getGreen()) / 255.0; double b = ((double) c.getBlue()) / 255.0; closeBlock(); // This ensures any paths are drawn in the previous initAlpha(c.getAlpha()); // colours pw.println("" + r + " " + g + " " + b + " rg " + r + " " + g + " " + b + " RG"); } if (paint instanceof MultipleGradientPaint) { closeBlock(); if ((paint instanceof RadialGradientPaint) || ((paint instanceof LinearGradientPaint) && (((LinearGradientPaint) paint).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE))) { shading = "gradient"; } else { pattern = "gradient"; } } if (paint instanceof TexturePaint) { closeBlock(); pattern = "texture"; } } private boolean useFunctionShading(MultipleGradientPaint fgrad) { return ((fgrad instanceof RadialGradientPaint) && fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE); } private Color[] convertColorSpace(Color[] colors, MultipleGradientPaint.ColorSpaceType colorSpaceType) { /*if (colorSpaceType == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { Color[] ret = new Color[colors.length]; for (int i = 0; i < colors.length; i++) { int argb = colors[i].getRGB(); int a = argb >>> 24; int r = srgbToLinear[(argb >> 16) & 0xff]; int g = srgbToLinear[(argb >> 8) & 0xff]; int b = srgbToLinear[(argb) & 0xff]; ret[i] = new Color(r, g, b, a); } return ret; }*/ //return colors; return colors; } private String generateRadialFunctionBody(RadialGradientPaint radGrad, boolean alpha) { double a = ((radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) * (radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) * (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) - radGrad.getRadius() * radGrad.getRadius()); String functionBody = "{\n" + matDf.format(radGrad.getFocusPoint().getX()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + " mul 2 mul\n" //stack size: 3 + matDf.format(radGrad.getFocusPoint().getY()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) + " mul 2 mul\n" + "add\n" //b, stack size: 3 + matDf.format(radGrad.getFocusPoint().getX()) + " 3 index sub\n" + "dup mul\n" + matDf.format(radGrad.getFocusPoint().getY()) + " 3 index sub\n" + "dup mul\n" + "add\n" //c, stack size: 4 + "1 index dup mul 4 " + matDf.format(a) + " mul 2 index mul sub\n" //D, stack size: 4 + "0 index 0 lt\n" + "{\n" + "1\n" + "}" + "{" + "0 index 0 gt\n" + "{\n" + "2 index neg 1 index sqrt add 2 " + matDf.format(a) + " mul div\n" // x1, stack size: 5 + "3 index neg 2 index sqrt sub 2 " + matDf.format(a) + " mul div\n" // x2, stack size: 6 + "0 index 2 index gt{0 index} {1 index} ifelse\n" + "exch pop exch pop\n" //x, stack size 5 + "}" + "{\n" + "2 index neg 2 " + matDf.format(a) + " mul div\n" // x, stack size 5 + "} ifelse\n" + "} ifelse\n" + "exch pop exch pop exch pop exch pop exch pop\n"; //remove index0,1,2,3,4 if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "dup\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { functionBody += "dup 1 gt {pop 1} if\n"; } else { functionBody += "dup 1 gt {dup floor sub} if\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "exch floor 2 mod 1 eq {" + "neg 1 add" + "}\n" + "if\n"; } int num = radGrad.getFractions().length; Color[] rcolors = convertColorSpace(radGrad.getColors(), radGrad.getColorSpace()); for (int i = 0; i < num - 1; i++) { functionBody += "dup " + radGrad.getFractions()[i] + " lt not 1 index " + radGrad.getFractions()[i + 1] + " gt not and\n{\n" + "0 index " + radGrad.getFractions()[i] + " sub " + (radGrad.getFractions()[i + 1] - radGrad.getFractions()[i]) + " div\n"; if (alpha) { functionBody += "0 index " + ((rcolors[i + 1].getAlpha() - rcolors[i].getAlpha()) / 255.0) + " mul " + (rcolors[i].getAlpha() / 255.0) + " add\n"; functionBody += "dup dup\n"; } else { if (radGrad.getColorSpace() == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { functionBody += "0 index " + ((srgbToLinear[rcolors[i + 1].getRed()] - srgbToLinear[rcolors[i].getRed()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getRed()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + ((srgbToLinear[rcolors[i + 1].getGreen()] - srgbToLinear[rcolors[i].getGreen()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getGreen()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + ((srgbToLinear[rcolors[i + 1].getBlue()] - srgbToLinear[rcolors[i].getBlue()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getBlue()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"; } else { functionBody += "0 index " + ((rcolors[i + 1].getRed() - rcolors[i].getRed()) / 255.0) + " mul " + (rcolors[i].getRed() / 255.0) + " add\n" + "1 index " + ((rcolors[i + 1].getGreen() - rcolors[i].getGreen()) / 255.0) + " mul " + (rcolors[i].getGreen() / 255.0) + " add\n" + "2 index " + ((rcolors[i + 1].getBlue() - rcolors[i].getBlue()) / 255.0) + " mul " + (rcolors[i].getBlue() / 255.0) + " add\n"; } } if (i < num - 2) { functionBody += "}\n{\n"; } } functionBody += "}if\n"; for (int i = 0; i < num - 2; i++) { functionBody += "}ifelse\n"; } functionBody += "}\n"; return functionBody; } private void initGradientPaint(MultipleGradientPaint grad, Shape fillShape) { if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D start = linGrad.getStartPoint(); Point2D end = linGrad.getEndPoint(); double deltaX = end.getX() - start.getX(); double deltaY = end.getY() - start.getY(); Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY); int colorCount = grad.getFractions().length; float fractions2[] = new float[colorCount * 2 - 1]; Color colors2[] = new Color[colorCount * 2 - 1]; float fractionsrev[] = new float[linGrad.getFractions().length]; Color colorsrev[] = new Color[linGrad.getColors().length]; for (int i = 0; i < fractionsrev.length; i++) { colorsrev[i] = linGrad.getColors()[i]; fractionsrev[i] = linGrad.getFractions()[i]; } for (int i = 0; i < colorCount; i++) { colors2[i] = colorsrev[i]; fractions2[i] = fractionsrev[i] / 2; } for (int i = 0; i < colorCount; i++) { colors2[colors2.length - i - 1] = colorsrev[i]; fractions2[colors2.length - i - 1] = 1f - fractionsrev[i] / 2; } LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT); grad = linGrad2; } List functions2Refs = new ArrayList<>(); Color[] colors = convertColorSpace(grad.getColors(), grad.getColorSpace()); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = colors[i - 1]; final Color color2 = colors[i]; final MultipleGradientPaint.ColorSpaceType colorSpace = grad.getColorSpace(); if (colorSpace == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { PDFStream function4 = new PDFStream(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1]".getBytes("UTF-8")); writeStream(os); } }; OutputStream f4Os = function4.getOutputStream(); int redDelta = srgbToLinear[color2.getRed()] - srgbToLinear[color1.getRed()]; int greenDelta = srgbToLinear[color2.getGreen()] - srgbToLinear[color1.getGreen()]; int blueDelta = srgbToLinear[color2.getBlue()] - srgbToLinear[color1.getBlue()]; String functionBody = "{" + "0 index " + redDelta + " mul " + srgbToLinear[color1.getRed()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + greenDelta + " mul " + srgbToLinear[color1.getGreen()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + blueDelta + " mul " + srgbToLinear[color1.getBlue()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "}"; try { f4Os.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(function4); functions2Refs.add(function4.getSerialID() + " 0 R"); } else { // PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getRed()) / 255.0f) + " " + (((float) color1.getGreen()) / 255.0f) + " " + (((float) color1.getBlue()) / 255.0f) + "] /C1 [" + (((float) color2.getRed()) / 255.0f) + " " + (((float) color2.getGreen()) / 255.0f) + " " + (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } } List functions2AlphaRefs = new ArrayList<>(); Color[] alphaColors = grad.getColors(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = alphaColors[i - 1]; final Color color2 = alphaColors[i]; PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + "] /C1 [" + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2AlphaRefs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFGradientFunction3(fgrad, functions2Refs); page.getPDFDocument().add(function3); PDFObject function3Alpha = new PDFGradientFunction3(fgrad, functions2AlphaRefs); page.getPDFDocument().add(function3Alpha); double glen = 0; double divisor = 1; double maxlen = 256; if ((fgrad instanceof LinearGradientPaint) && (fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE)) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; Point2D startPoint = new Point2D.Double(); Point2D endPoint = new Point2D.Double(); startPoint = linGrad.getStartPoint(); endPoint = linGrad.getEndPoint(); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); glen = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (glen > maxlen) { divisor = glen / maxlen; glen = maxlen; } } final double flen = glen; PDFStream radialFunction; PDFStream radialAlphaFunction; if (useFunctionShading(fgrad)) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; radialFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; radialAlphaFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; //PDF reference, page 176 /*double b = 2 * (focalX - x) * (centerX - focalX) + 2 * (focalY - y) * (centerY - focalY); double c = (focalX - x) * (focalX - x) + (focalY - y) * (focalY - y); double D = b * b - 4 * a * c;*/ //(-b + Math.sqrt(D)) / (2 * a) //D = b * b - 4 * a * c; String functionBody = generateRadialFunctionBody(radGrad, false); OutputStream funOs = radialFunction.getOutputStream(); try { funOs.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } String alphaFunctionBody = generateRadialFunctionBody(radGrad, true); funOs = radialAlphaFunction.getOutputStream(); try { funOs.write(alphaFunctionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(radialFunction); page.getPDFDocument().add(radialAlphaFunction); } else { radialFunction = null; radialAlphaFunction = null; } PDFObject shadingObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3, radialFunction); page.getPDFDocument().add(shadingObj); PDFObject shadingAlphaObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3Alpha, radialAlphaFunction); page.getPDFDocument().add(shadingAlphaObj); shadingCount++; final int fCurrentShadingCount = shadingCount; PDFStream alphaObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Group << /CS /DeviceGray /S /Transparency >>\n".getBytes("UTF-8")); os.write("/Type /XObject\n".getBytes("UTF-8")); os.write("/Resources <<\n".getBytes("UTF-8")); os.write("/Shading <<".getBytes("UTF-8")); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(">>\n".getBytes("UTF-8")); //shading os.write(">>\n".getBytes("UTF-8")); //resources os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(alphaObject); String alphaExtGState = "/GradAlpha" + fCurrentShadingCount + " <<" + "/SMask <<" + "/Type /Mask\n" + "/S /Luminosity\n" + "/G " + alphaObject.getSerialID() + " 0 R" + ">>" + ">>"; if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D startPoint = linGrad.getStartPoint(); Point2D endPoint = linGrad.getEndPoint(); Point2D startPointTrans = new Point2D.Double(); Point2D endPointTrans = new Point2D.Double(); transform.transform(linGrad.getStartPoint(), startPointTrans); transform.transform(linGrad.getEndPoint(), endPointTrans); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); AffineTransform m = new AffineTransform(); if (usePTransform) { m.concatenate(pTransform); } m.concatenate(transform); m.concatenate(AffineTransform.getTranslateInstance(startPoint.getX(), endPoint.getY())); m.concatenate(AffineTransform.getScaleInstance(divisor, divisor)); m.concatenate(AffineTransform.getRotateInstance(-alfa)); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); double w; double h; w = 1; h = flen; os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes("UTF-8")); os.write(("/XStep " + w + "\n").getBytes("UTF-8")); os.write(("/YStep " + h + "\n").getBytes("UTF-8")); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes("UTF-8")); os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes("UTF-8")); patOs.write(("/Shin" + shadingCount + " sh").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(innerPattern); page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; return; } currentAlpha = -1; page.addExtGStateResource(alphaExtGState); pw.println("/GradAlpha" + fCurrentShadingCount + " gs"); page.addShadingResource("/Sh" + shadingCount + " " + shadingObj.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } /** * Hack: * Images, which have method boolean isJpeg() and byte[] getImageData() can be stored as JPEGs * You must implement an Image which has these methods to be properly able to save JPEGS */ private byte[] getImageJpegData(Image img) { byte[] jpegImageData = null; try { Method jpegMethod = img.getClass().getDeclaredMethod("isJpeg"); boolean isJpeg = (boolean) jpegMethod.invoke(img); if (isJpeg) { Method getImageDataMethod = img.getClass().getDeclaredMethod("getImageData"); jpegImageData = (byte[]) getImageDataMethod.invoke(img); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //ignore } return jpegImageData; } private byte[] getImageAlphaChannel(Image img) { try { Method getAlphaChannelMethod = img.getClass().getDeclaredMethod("getAlphaChannel"); return (byte[]) getAlphaChannelMethod.invoke(img); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //ignore } return null; } private void initTexturePaint(TexturePaint texturePaint) { byte[] jpegImageData = getImageJpegData(texturePaint.getImage()); byte[] alphaChannel = null; if (jpegImageData != null) { alphaChannel = getImageAlphaChannel(texturePaint.getImage()); } BufferedImage img = texturePaint.getImage(); PDFMask mask = alphaChannel == null ? new PDFMask(img) : new PDFMask(alphaChannel, img.getWidth(), img.getHeight()); page.getPDFDocument().add(mask); Rectangle2D anchorRect = texturePaint.getAnchorRect(); final double w = anchorRect.getWidth(); final double h = anchorRect.getHeight(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (page.getPDFDocument().isImageCached(img, interpolate)) { image = page.getPDFDocument().getCachedImage(img, interpolate); } else { if (jpegImageData != null) { image = new PDFImage(jpegImageData, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R", interpolate); } // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); page.getPDFDocument().cacheImage(img, image); } AffineTransform m = new AffineTransform(); AffineTransform ptt = new AffineTransform(); if (usePTransform) { ptt.concatenate(pTransform); } ptt.concatenate(transform); m.concatenate(ptt); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes("UTF-8")); os.write(("/XStep " + matDf.format(w) + "\n").getBytes("UTF-8")); os.write(("/YStep " + matDf.format(h) + "\n").getBytes("UTF-8")); os.write(("/Resources << ").getBytes("UTF-8")); os.write("/XObject << ".getBytes("UTF-8")); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(" >> ".getBytes("UTF-8")); os.write((">>\n").getBytes("UTF-8")); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); PrintWriter patwriter = new PrintWriter(patOs); AffineTransform transformToSet; transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); patwriter.flush(); page.getPDFDocument().add(innerPattern); shadingCount++; page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; } /** * Not implemented, as this is not supported in the PDF specification. */ @Override public void setPaintMode() { } /** * Sets a rendering hint * * @param arg0 * @param arg1 */ @Override public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ @Override public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ @Override public void setStroke(Stroke s) { this.stroke = s; if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ @Override public void setTransform(AffineTransform t) { setNewTranform(new AffineTransform(t)); } /** * Not implemented, as this is not supported in the PDF specification. * * @param c1 Color to xor with */ @Override public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ @Override public void shear(double shx, double shy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.shear(shx, shy); setNewTranform(newTransform); } /** * @see Graphics2D#transform(AffineTransform) */ @Override public void transform(AffineTransform tx) { AffineTransform newTransform = new AffineTransform(transform); newTransform.concatenate(tx); setNewTranform(newTransform); } /** * @see Graphics2D#translate(double, double) */ @Override public void translate(double tx, double ty) { AffineTransform newTransform = new AffineTransform(transform); newTransform.translate(tx, ty); setNewTranform(newTransform); } /** * @see Graphics#translate(int, int) */ @Override public void translate(int x, int y) { translate((double) x, (double) y); } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x, float y, float tx, float ty) { float nx = x, ny = y; float ntx = tx, nty = ty; nx = (float) (x - tx); ny = (float) (y - ty); return "" + df.format(nx) + " " + df.format(ny) + " "; } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x, float y) { Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); if (usePTransform) { pTransform.transform(ptSrc, ptDst); } return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " "; } private void setNewTranform(AffineTransform t) { closeBlock(); if (true) { //return; } AffineTransform newTransform = new AffineTransform(t); AffineTransform transformToSet = new AffineTransform(newTransform); if (usePTransform) { if (transform != null) { AffineTransform aInv = new AffineTransform(transform); try { aInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } AffineTransform pInv = new AffineTransform(pTransform); try { pInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } transformToSet = new AffineTransform(); transformToSet.concatenate(aInv); transformToSet.concatenate(pInv); transformToSet.concatenate(pTransform); transformToSet.concatenate(newTransform); } else { transformToSet.preConcatenate(pTransform); } } if (clip != null) { AffineTransform inv = new AffineTransform(newTransform); try { inv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } clip = new Area(transform.createTransformedShape(inv.createTransformedShape(clip))); clipRectangle = clip.getBounds(); } transform = newTransform; pw.println("" + matDf.format(transformToSet.getScaleX()) + " " + "" + matDf.format(transformToSet.getShearY()) + " " + "" + matDf.format(transformToSet.getShearX()) + " " + "" + matDf.format(transformToSet.getScaleY()) + " " + "" + matDf.format(transformToSet.getTranslateX()) + " " + "" + matDf.format(transformToSet.getTranslateY()) + " cm" ); } private void saveState() { pw.println("q"); } private void restoreState() { pw.println("Q"); } public void drawXObject(Graphics g) { if (g instanceof PDFGraphics) { int objId = ((PDFGraphics) g).objId; pw.println("/MyObj" + objId + " Do"); } } public Graphics2D createXObject() { final PDFPage newPage = new PDFPage(page.pageFormat) { @Override public void addToProcset(String proc) { page.addToProcset(proc); } }; newPage.pdfDocument = page.pdfDocument; PDFStream retObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Type /XObject\n".getBytes("UTF-8")); newPage.writeResources(os); os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; page.getPDFDocument().add(retObject); OutputStream os = retObject.getOutputStream(); RawPrintWriter pw2 = new RawPrintWriter(os); page.addXObject("/MyObj" + retObject.getSerialID() + " " + retObject.getSerialID() + " 0 R"); PDFGraphics g = new PDFGraphics(); g.usePTransform = false; g.init(newPage, pw2); g.objId = retObject.getSerialID(); //g.setTransform(new AffineTransform()); return g; } } \ No newline at end of file +/* * $Id: PDFGraphics.java,v 1.6 2007/09/22 12:58:40 gil1 Exp $ * * $Date: 2007/09/22 12:58:40 $ * * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package gnu.jpdf; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Composite; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.Image; import java.awt.LinearGradientPaint; import java.awt.MultipleGradientPaint; import java.awt.Paint; import java.awt.Polygon; import java.awt.RadialGradientPaint; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.RenderingHints.Key; import java.awt.Shape; import java.awt.Stroke; import java.awt.TexturePaint; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Ellipse2D; import java.awt.geom.NoninvertibleTransformException; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.BufferedImageOp; import java.awt.image.ColorModel; import java.awt.image.ImageObserver; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.RenderableImage; import java.awt.print.PageFormat; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import jdk.internal.reflect.Reflection; /** * This class is our implementation of AWT's Graphics class. It provides a Java * standard way of rendering into a PDF Document's Page. * * @author Peter T Mount, http://www.retep.org.uk/pdf/ * @author Eric Z. Beard, ericzbeard@hotmail.com * @author Gilbert DeLeeuw, gil1@users.sourceforge.net * @version $Revision: 1.6 $, $Date: 2007/09/22 12:58:40 $ * @see gnu.jpdf.PDFGraphics */ public class PDFGraphics extends Graphics2D implements Serializable { /** * One degree in radians */ private static final double degrees_to_radians = Math.PI / 180.0; private static final int FILL = 1; private static final int STROKE = 2; private static final int CLIP = 3; private static final AffineTransform IDENTITY = new AffineTransform(); private static final Stroke DEF_STROKE = new BasicStroke(); /* * NOTE: The original class is the work of Peter T. Mount, who released it * in the uk.org.retep.pdf package. It was modified by Eric Z. Beard as * follows: * The package name was changed to gnu.pdf. * The formatting was changed a little bit. * This used to subclass an abstract class in a different package with * the same name (confusing). Now it's one concrete class. * drawImage() was implemented * It is still licensed under the LGPL. */ // Implementation notes: // // Pages 333-335 of the PDF Reference Manual // // Unless absolutely required, use the moveto, lineto and rectangle // operators to perform those actions. // They contain some extra optimizations // which will reduce the output size by up to half in some cases. // // About fill operators: For correct operation, any fill operation should // start with closeBlock(), which will ensure any previous path is completed, // otherwise you may find the fill will include previous items private static final DecimalFormat df = new DecimalFormat("#.###", new DecimalFormatSymbols(Locale.ENGLISH)); private static final DecimalFormat matDf = new DecimalFormat("0", new DecimalFormatSymbols(Locale.ENGLISH)); private static final DecimalFormat colorDf = new DecimalFormat("#.#####", new DecimalFormatSymbols(Locale.ENGLISH)); static { matDf.setMaximumFractionDigits(340); df.setGroupingUsed(false); matDf.setGroupingUsed(false); colorDf.setGroupingUsed(false); } private Set usedImagesUseMainResources = new HashSet(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * * @see #create */ private boolean child; private Area clip; private AffineTransform clipTransform; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: The last known moveto/lineto x coordinate * * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: The last known moveto/lineto y coordinate * * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; private AffineTransform paintTransform; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: This is written to the stream when the newPath() * is called. np then clears this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties private AffineTransform transform; /** * Part of the optimizer: The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering private String shading = null; private String pattern = null; private Set usedAlphas = new HashSet<>(); private Set gsBlendModes = new HashSet<>(); private int currentAlpha = 255; private String blendMode; private int shadingCount = 0; private int objId = 0; private boolean usePTransform = true; private static int[] srgbToLinear = new int[256]; private static int[] linearToSrgb = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(i); linearToSrgb[i] = convertLinearRGBtoSRGB(i); } } private static int convertSRGBtoLinearRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.04045f) { output = input / 12.92f; } else { output = (float) Math.pow((input + 0.055) / 1.055, 2.4); } return Math.round(output * 255.0f); } private static int convertLinearRGBtoSRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.0031308) { output = input * 12.92f; } else { output = (1.055f * ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f; } return Math.round(output * 255.0f); } /** * @see Graphics2D#addRenderingHints(Map) */ @Override public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc, double ayc, double width, double height, double ang1, double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1 % 360.0) * degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width * cos0; y0 = ayc + height * sin0; // NB: !clockwise here as Java Space is inverted to User Space if (!clockwise) { // Quadrant reduction while (ang1 < ang2) { ang2 -= 360.0; } while ((adiff = ang2 - ang1) < -90.0) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while (ang2 < ang1) { ang2 += 360.0; } while ((adiff = ang2 - ang1) > 90.0) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width * sin0; double yt = y0 + trad * height * cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path Important: We * write directly to the stream here, because this method operates in User * space, rather than Java space. * * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w, double h, double x0, double y0, double x3, double y3, double xt, double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx * dx + dy * dy; double w2 = w * w, h2 = h * h; double r2 = w2 + h2; double fw = 0.0, fh = 0.0; if (dist < (r2 * 1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0; } // The path must have a starting point if (first) { moveto(x0, y0); } double x = x0 + ((xt - x0) * fw); double y = y0 + ((yt - y0) * fh); x0 = x3 + ((xt - x3) * fw); y0 = y3 + ((yt - y3) * fh); // Finally the actual curve. curveto(x, y, x0, y0, x3, y3); } /** * This simply draws a White Rectangle to clear the area * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clearRect(int x, int y, int w, int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x, y, w, h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ @Override public void clip(Shape s) { if (s == null) { setClip(null); return; } Area newClip; if (clip == null) { newClip = new Area(s); } else { newClip = (Area) clip.clone(); newClip.intersect(new Area(s)); } setClip(newClip); } /** * This extra method allows PDF users to clip to a Polygon. * *

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (page.getPDFDocument().isImageCached(img, interpolate)) { image = page.getPDFDocument().getCachedImage(img, interpolate); //it was previously only part of pattern if (!usedImagesUseMainResources.contains(new ImageInterpolate(img, interpolate))) { page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); usedImagesUseMainResources.add(new ImageInterpolate(img, interpolate)); } } else { byte[] jpegImageData = getImageJpegData(img); byte[] alphaChannel = null; if (jpegImageData != null) { alphaChannel = getImageAlphaChannel(img); } PDFMask mask = alphaChannel == null ? new PDFMask(img) : new PDFMask(alphaChannel, img.getWidth(null), img.getHeight(null)); page.getPDFDocument().add(mask); //TODO: We should propably cache different images for interpolate vs no interpolate, // But for JPEXS FFDec it's enough I think. // I tried smooth/nonsmooth bitmap in Flash Pro CS6 and it always produced two separate imagetags if (jpegImageData != null) { image = new PDFImage(jpegImageData, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); page.getPDFDocument().cacheImage(img, image); page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); usedImagesUseMainResources.add(new ImageInterpolate(img, interpolate)); } initAlpha(255); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h); AffineTransform transformToSet = newTransform; pw.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

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

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

* Draws an oval

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

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

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

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

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

* Not implemented

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

* Draws a filled oval

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

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

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

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

* *

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

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

* Sets the clipping region to that of a Shape. * * @param s Shape to clip to. */ @Override public void setClip(Shape s) { closeBlock(); if (clip != null) { restoreState(); AffineTransform currentTransform = transform; transform = clipTransform; setTransform(currentTransform); } if (s == null) { clip = null; return; } clipTransform = transform; clip = new Area(s); clipRectangle = s.getBounds(); saveState(); followPath(s, CLIP); pw.println("n"); //setClip(r.x, r.y, r.width, r.height); } /** * Sets the color for drawing * * @param c Color to use */ @Override public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ @Override public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * * @param f java.awt.Font to set to. */ @Override public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if (font != f) { font = f; pdffont = page.getFont("/Type1", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println("" + lineCap + " J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println("" + lineJoin + " j"); } } /** * This extension allows the width of the drawn line to be set * * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println("" + width + " w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println("" + limit + " M"); } } private void initAlpha(int alpha) { if (currentAlpha != alpha) { if (blendMode != null) { setBlendMode(blendMode, alpha); return; } String gsId = "/GSAlpha" + alpha; currentAlpha = alpha; if (!usedAlphas.contains(alpha)) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } } private void setBlendMode(String mode, int alpha) { if (alpha == currentAlpha && mode == this.blendMode) { return; } if (alpha < 0) { alpha = 255; } String gsName = "/GSBlend" + mode + "Alpha" + alpha; if (!gsBlendModes.contains(gsName)) { page.addExtGStateResource(gsName + " <>"); } pw.println(gsName + " gs"); this.blendMode = mode; this.currentAlpha = alpha; } public void setBlendMode(String mode) { setBlendMode(mode, currentAlpha); } /** * Sets the paint for drawing * * @param paint Paint to use */ @Override public void setPaint(Paint paint) { this.paint = paint; this.shading = null; this.pattern = null; this.paintTransform = null; if (paint instanceof Color) { Color c = (Color) paint; double r = ((double) c.getRed()) / 255.0; double g = ((double) c.getGreen()) / 255.0; double b = ((double) c.getBlue()) / 255.0; closeBlock(); // This ensures any paths are drawn in the previous initAlpha(c.getAlpha()); // colours pw.println("" + colorDf.format(r) + " " + colorDf.format(g) + " " + colorDf.format(b) + " rg " + colorDf.format(r) + " " + colorDf.format(g) + " " + colorDf.format(b) + " RG"); } if (paint instanceof MultipleGradientPaint) { closeBlock(); if ((paint instanceof RadialGradientPaint) || ((paint instanceof LinearGradientPaint) && (((LinearGradientPaint) paint).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE))) { shading = "gradient"; } else { pattern = "gradient"; } } if (paint instanceof TexturePaint) { closeBlock(); pattern = "texture"; } } private boolean useFunctionShading(MultipleGradientPaint fgrad) { return ((fgrad instanceof RadialGradientPaint) && fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE); } private Color[] convertColorSpace(Color[] colors, MultipleGradientPaint.ColorSpaceType colorSpaceType) { /*if (colorSpaceType == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { Color[] ret = new Color[colors.length]; for (int i = 0; i < colors.length; i++) { int argb = colors[i].getRGB(); int a = argb >>> 24; int r = srgbToLinear[(argb >> 16) & 0xff]; int g = srgbToLinear[(argb >> 8) & 0xff]; int b = srgbToLinear[(argb) & 0xff]; ret[i] = new Color(r, g, b, a); } return ret; }*/ //return colors; return colors; } private String generateRadialFunctionBody(RadialGradientPaint radGrad, boolean alpha) { double a = ((radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) * (radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) * (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) - radGrad.getRadius() * radGrad.getRadius()); String functionBody = "{\n" + matDf.format(radGrad.getFocusPoint().getX()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + " mul 2 mul\n" //stack size: 3 + matDf.format(radGrad.getFocusPoint().getY()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) + " mul 2 mul\n" + "add\n" //b, stack size: 3 + matDf.format(radGrad.getFocusPoint().getX()) + " 3 index sub\n" + "dup mul\n" + matDf.format(radGrad.getFocusPoint().getY()) + " 3 index sub\n" + "dup mul\n" + "add\n" //c, stack size: 4 + "1 index dup mul 4 " + matDf.format(a) + " mul 2 index mul sub\n" //D, stack size: 4 + "0 index 0 lt\n" + "{\n" + "1\n" + "}" + "{" + "0 index 0 gt\n" + "{\n" + "2 index neg 1 index sqrt add 2 " + matDf.format(a) + " mul div\n" // x1, stack size: 5 + "3 index neg 2 index sqrt sub 2 " + matDf.format(a) + " mul div\n" // x2, stack size: 6 + "0 index 2 index gt{0 index} {1 index} ifelse\n" + "exch pop exch pop\n" //x, stack size 5 + "}" + "{\n" + "2 index neg 2 " + matDf.format(a) + " mul div\n" // x, stack size 5 + "} ifelse\n" + "} ifelse\n" + "exch pop exch pop exch pop exch pop exch pop\n"; //remove index0,1,2,3,4 if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "dup\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { functionBody += "dup 1 gt {pop 1} if\n"; } else { functionBody += "dup 1 gt {dup floor sub} if\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "exch floor 2 mod 1 eq {" + "neg 1 add" + "}\n" + "if\n"; } int num = radGrad.getFractions().length; Color[] rcolors = convertColorSpace(radGrad.getColors(), radGrad.getColorSpace()); for (int i = 0; i < num - 1; i++) { functionBody += "dup " + radGrad.getFractions()[i] + " lt not 1 index " + radGrad.getFractions()[i + 1] + " gt not and\n{\n" + "0 index " + radGrad.getFractions()[i] + " sub " + (radGrad.getFractions()[i + 1] - radGrad.getFractions()[i]) + " div\n"; if (alpha) { functionBody += "0 index " + ((rcolors[i + 1].getAlpha() - rcolors[i].getAlpha()) / 255.0) + " mul " + (rcolors[i].getAlpha() / 255.0) + " add\n"; functionBody += "dup dup\n"; } else { if (radGrad.getColorSpace() == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { functionBody += "0 index " + ((srgbToLinear[rcolors[i + 1].getRed()] - srgbToLinear[rcolors[i].getRed()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getRed()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + ((srgbToLinear[rcolors[i + 1].getGreen()] - srgbToLinear[rcolors[i].getGreen()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getGreen()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + ((srgbToLinear[rcolors[i + 1].getBlue()] - srgbToLinear[rcolors[i].getBlue()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getBlue()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"; } else { functionBody += "0 index " + ((rcolors[i + 1].getRed() - rcolors[i].getRed()) / 255.0) + " mul " + (rcolors[i].getRed() / 255.0) + " add\n" + "1 index " + ((rcolors[i + 1].getGreen() - rcolors[i].getGreen()) / 255.0) + " mul " + (rcolors[i].getGreen() / 255.0) + " add\n" + "2 index " + ((rcolors[i + 1].getBlue() - rcolors[i].getBlue()) / 255.0) + " mul " + (rcolors[i].getBlue() / 255.0) + " add\n"; } } if (i < num - 2) { functionBody += "}\n{\n"; } } functionBody += "}if\n"; for (int i = 0; i < num - 2; i++) { functionBody += "}ifelse\n"; } functionBody += "}\n"; return functionBody; } private void initGradientPaint(MultipleGradientPaint grad, Shape fillShape) { if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D start = linGrad.getStartPoint(); Point2D end = linGrad.getEndPoint(); double deltaX = end.getX() - start.getX(); double deltaY = end.getY() - start.getY(); Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY); int colorCount = grad.getFractions().length; float fractions2[] = new float[colorCount * 2 - 1]; Color colors2[] = new Color[colorCount * 2 - 1]; float fractionsrev[] = new float[linGrad.getFractions().length]; Color colorsrev[] = new Color[linGrad.getColors().length]; for (int i = 0; i < fractionsrev.length; i++) { colorsrev[i] = linGrad.getColors()[i]; fractionsrev[i] = linGrad.getFractions()[i]; } for (int i = 0; i < colorCount; i++) { colors2[i] = colorsrev[i]; fractions2[i] = fractionsrev[i] / 2; } for (int i = 0; i < colorCount; i++) { colors2[colors2.length - i - 1] = colorsrev[i]; fractions2[colors2.length - i - 1] = 1f - fractionsrev[i] / 2; } LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT); grad = linGrad2; } List functions2Refs = new ArrayList<>(); Color[] colors = convertColorSpace(grad.getColors(), grad.getColorSpace()); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = colors[i - 1]; final Color color2 = colors[i]; final MultipleGradientPaint.ColorSpaceType colorSpace = grad.getColorSpace(); if (colorSpace == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { PDFStream function4 = new PDFStream(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1]".getBytes("UTF-8")); writeStream(os); } }; OutputStream f4Os = function4.getOutputStream(); int redDelta = srgbToLinear[color2.getRed()] - srgbToLinear[color1.getRed()]; int greenDelta = srgbToLinear[color2.getGreen()] - srgbToLinear[color1.getGreen()]; int blueDelta = srgbToLinear[color2.getBlue()] - srgbToLinear[color1.getBlue()]; String functionBody = "{" + "0 index " + redDelta + " mul " + srgbToLinear[color1.getRed()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + greenDelta + " mul " + srgbToLinear[color1.getGreen()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + blueDelta + " mul " + srgbToLinear[color1.getBlue()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "}"; try { f4Os.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(function4); functions2Refs.add(function4.getSerialID() + " 0 R"); } else { // PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getRed()) / 255.0f) + " " + (((float) color1.getGreen()) / 255.0f) + " " + (((float) color1.getBlue()) / 255.0f) + "] /C1 [" + (((float) color2.getRed()) / 255.0f) + " " + (((float) color2.getGreen()) / 255.0f) + " " + (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } } List functions2AlphaRefs = new ArrayList<>(); Color[] alphaColors = grad.getColors(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = alphaColors[i - 1]; final Color color2 = alphaColors[i]; PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + "] /C1 [" + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2AlphaRefs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFGradientFunction3(fgrad, functions2Refs); page.getPDFDocument().add(function3); PDFObject function3Alpha = new PDFGradientFunction3(fgrad, functions2AlphaRefs); page.getPDFDocument().add(function3Alpha); double glen = 0; double divisor = 1; double maxlen = 256; if ((fgrad instanceof LinearGradientPaint) && (fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE)) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; Point2D startPoint = new Point2D.Double(); Point2D endPoint = new Point2D.Double(); startPoint = linGrad.getStartPoint(); endPoint = linGrad.getEndPoint(); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); glen = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (glen > maxlen) { divisor = glen / maxlen; glen = maxlen; } } final double flen = glen; PDFStream radialFunction; PDFStream radialAlphaFunction; if (useFunctionShading(fgrad)) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; radialFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; radialAlphaFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; //PDF reference, page 176 /*double b = 2 * (focalX - x) * (centerX - focalX) + 2 * (focalY - y) * (centerY - focalY); double c = (focalX - x) * (focalX - x) + (focalY - y) * (focalY - y); double D = b * b - 4 * a * c;*/ //(-b + Math.sqrt(D)) / (2 * a) //D = b * b - 4 * a * c; String functionBody = generateRadialFunctionBody(radGrad, false); OutputStream funOs = radialFunction.getOutputStream(); try { funOs.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } String alphaFunctionBody = generateRadialFunctionBody(radGrad, true); funOs = radialAlphaFunction.getOutputStream(); try { funOs.write(alphaFunctionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(radialFunction); page.getPDFDocument().add(radialAlphaFunction); } else { radialFunction = null; radialAlphaFunction = null; } PDFObject shadingObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3, radialFunction); page.getPDFDocument().add(shadingObj); PDFObject shadingAlphaObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3Alpha, radialAlphaFunction); page.getPDFDocument().add(shadingAlphaObj); shadingCount++; final int fCurrentShadingCount = shadingCount; PDFStream alphaObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Group << /CS /DeviceGray /S /Transparency >>\n".getBytes("UTF-8")); os.write("/Type /XObject\n".getBytes("UTF-8")); os.write("/Resources <<\n".getBytes("UTF-8")); os.write("/Shading <<".getBytes("UTF-8")); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(">>\n".getBytes("UTF-8")); //shading os.write(">>\n".getBytes("UTF-8")); //resources os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(alphaObject); String alphaExtGState = "/GradAlpha" + fCurrentShadingCount + " <<" + "/SMask <<" + "/Type /Mask\n" + "/S /Luminosity\n" + "/G " + alphaObject.getSerialID() + " 0 R" + ">>" + ">>"; if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D startPoint = linGrad.getStartPoint(); Point2D endPoint = linGrad.getEndPoint(); Point2D startPointTrans = new Point2D.Double(); Point2D endPointTrans = new Point2D.Double(); transform.transform(linGrad.getStartPoint(), startPointTrans); transform.transform(linGrad.getEndPoint(), endPointTrans); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); AffineTransform m = new AffineTransform(); if (usePTransform) { m.concatenate(pTransform); } m.concatenate(transform); m.concatenate(AffineTransform.getTranslateInstance(startPoint.getX(), endPoint.getY())); m.concatenate(AffineTransform.getScaleInstance(divisor, divisor)); m.concatenate(AffineTransform.getRotateInstance(-alfa)); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); double w; double h; w = 1; h = flen; os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes("UTF-8")); os.write(("/XStep " + w + "\n").getBytes("UTF-8")); os.write(("/YStep " + h + "\n").getBytes("UTF-8")); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes("UTF-8")); os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes("UTF-8")); patOs.write(("/Shin" + shadingCount + " sh").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(innerPattern); page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; return; } currentAlpha = -1; page.addExtGStateResource(alphaExtGState); pw.println("/GradAlpha" + fCurrentShadingCount + " gs"); page.addShadingResource("/Sh" + shadingCount + " " + shadingObj.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } /** * Hack: * Images, which have method boolean isJpeg() and byte[] getImageData() can be stored as JPEGs * You must implement an Image which has these methods to be properly able to save JPEGS */ private byte[] getImageJpegData(Image img) { byte[] jpegImageData = null; try { Method jpegMethod = img.getClass().getDeclaredMethod("isJpeg"); boolean isJpeg = (boolean) jpegMethod.invoke(img); if (isJpeg) { Method getImageDataMethod = img.getClass().getDeclaredMethod("getImageData"); jpegImageData = (byte[]) getImageDataMethod.invoke(img); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //ignore } return jpegImageData; } private byte[] getImageAlphaChannel(Image img) { try { Method getAlphaChannelMethod = img.getClass().getDeclaredMethod("getAlphaChannel"); return (byte[]) getAlphaChannelMethod.invoke(img); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //ignore } return null; } private void initTexturePaint(TexturePaint texturePaint) { byte[] jpegImageData = getImageJpegData(texturePaint.getImage()); byte[] alphaChannel = null; if (jpegImageData != null) { alphaChannel = getImageAlphaChannel(texturePaint.getImage()); } BufferedImage img = texturePaint.getImage(); PDFMask mask = alphaChannel == null ? new PDFMask(img) : new PDFMask(alphaChannel, img.getWidth(), img.getHeight()); page.getPDFDocument().add(mask); Rectangle2D anchorRect = texturePaint.getAnchorRect(); final double w = anchorRect.getWidth(); final double h = anchorRect.getHeight(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (page.getPDFDocument().isImageCached(img, interpolate)) { image = page.getPDFDocument().getCachedImage(img, interpolate); } else { if (jpegImageData != null) { image = new PDFImage(jpegImageData, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R", interpolate); } // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); page.getPDFDocument().cacheImage(img, image); } AffineTransform m = new AffineTransform(); AffineTransform ptt = new AffineTransform(); if (usePTransform) { ptt.concatenate(pTransform); } ptt.concatenate(transform); m.concatenate(ptt); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes("UTF-8")); os.write(("/XStep " + matDf.format(w) + "\n").getBytes("UTF-8")); os.write(("/YStep " + matDf.format(h) + "\n").getBytes("UTF-8")); os.write(("/Resources << ").getBytes("UTF-8")); os.write("/XObject << ".getBytes("UTF-8")); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(" >> ".getBytes("UTF-8")); os.write((">>\n").getBytes("UTF-8")); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); PrintWriter patwriter = new PrintWriter(patOs); AffineTransform transformToSet; transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); patwriter.flush(); page.getPDFDocument().add(innerPattern); shadingCount++; page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; } /** * Not implemented, as this is not supported in the PDF specification. */ @Override public void setPaintMode() { } /** * Sets a rendering hint * * @param arg0 * @param arg1 */ @Override public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ @Override public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ @Override public void setStroke(Stroke s) { this.stroke = s; if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ @Override public void setTransform(AffineTransform t) { setNewTranform(new AffineTransform(t)); } /** * Not implemented, as this is not supported in the PDF specification. * * @param c1 Color to xor with */ @Override public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ @Override public void shear(double shx, double shy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.shear(shx, shy); setNewTranform(newTransform); } /** * @see Graphics2D#transform(AffineTransform) */ @Override public void transform(AffineTransform tx) { AffineTransform newTransform = new AffineTransform(transform); newTransform.concatenate(tx); setNewTranform(newTransform); } /** * @see Graphics2D#translate(double, double) */ @Override public void translate(double tx, double ty) { AffineTransform newTransform = new AffineTransform(transform); newTransform.translate(tx, ty); setNewTranform(newTransform); } /** * @see Graphics#translate(int, int) */ @Override public void translate(int x, int y) { translate((double) x, (double) y); } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x, float y, float tx, float ty) { float nx = x, ny = y; float ntx = tx, nty = ty; nx = (float) (x - tx); ny = (float) (y - ty); return "" + df.format(nx) + " " + df.format(ny) + " "; } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x, float y) { Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); if (usePTransform) { pTransform.transform(ptSrc, ptDst); } return "" + df.format(ptDst.getX()) + " " + df.format(ptDst.getY()) + " "; } private void setNewTranform(AffineTransform t) { closeBlock(); if (true) { //return; } AffineTransform newTransform = new AffineTransform(t); AffineTransform transformToSet = new AffineTransform(newTransform); if (usePTransform) { if (transform != null) { AffineTransform aInv = new AffineTransform(transform); try { aInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } AffineTransform pInv = new AffineTransform(pTransform); try { pInv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } transformToSet = new AffineTransform(); transformToSet.concatenate(aInv); transformToSet.concatenate(pInv); transformToSet.concatenate(pTransform); transformToSet.concatenate(newTransform); } else { transformToSet.preConcatenate(pTransform); } } if (clip != null) { AffineTransform inv = new AffineTransform(newTransform); try { inv.invert(); } catch (NoninvertibleTransformException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } clip = new Area(transform.createTransformedShape(inv.createTransformedShape(clip))); clipRectangle = clip.getBounds(); } transform = newTransform; pw.println("" + matDf.format(transformToSet.getScaleX()) + " " + "" + matDf.format(transformToSet.getShearY()) + " " + "" + matDf.format(transformToSet.getShearX()) + " " + "" + matDf.format(transformToSet.getScaleY()) + " " + "" + matDf.format(transformToSet.getTranslateX()) + " " + "" + matDf.format(transformToSet.getTranslateY()) + " cm" ); } private void saveState() { pw.println("q"); } private void restoreState() { pw.println("Q"); } public void drawXObject(Graphics g) { if (g instanceof PDFGraphics) { int objId = ((PDFGraphics) g).objId; pw.println("/MyObj" + objId + " Do"); } } public Graphics2D createXObject() { final PDFPage newPage = new PDFPage(page.pageFormat) { @Override public void addToProcset(String proc) { page.addToProcset(proc); } }; newPage.pdfDocument = page.pdfDocument; PDFStream retObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Type /XObject\n".getBytes("UTF-8")); newPage.writeResources(os); os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; page.getPDFDocument().add(retObject); OutputStream os = retObject.getOutputStream(); RawPrintWriter pw2 = new RawPrintWriter(os); page.addXObject("/MyObj" + retObject.getSerialID() + " " + retObject.getSerialID() + " 0 R"); PDFGraphics g = new PDFGraphics(); g.usePTransform = false; g.init(newPage, pw2); g.objId = retObject.getSerialID(); //g.setTransform(new AffineTransform()); return g; } } \ No newline at end of file