From 49abcd44470ad43e5d35b447c32678aa950c88ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 9 Apr 2023 20:37:54 +0200 Subject: [PATCH] GNUJPdf - draw image with scaling --- lib/gnujpdf.jar | Bin 211432 -> 211845 bytes libsrc/ffdec_lib/lib/gnujpdf.jar | Bin 211432 -> 211845 bytes libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 2919d7ee17491429aff700b6509a84c99d5de58b..093be79f3cfd7bee069f2212cd21c0332cda8954 100644 GIT binary patch delta 32584 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 G_5T2!AGfLi diff --git a/libsrc/ffdec_lib/lib/gnujpdf.jar b/libsrc/ffdec_lib/lib/gnujpdf.jar index 2919d7ee17491429aff700b6509a84c99d5de58b..093be79f3cfd7bee069f2212cd21c0332cda8954 100644 GIT binary patch delta 32584 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 G_5T2!AGfLi diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java index 1460d0570..728682bd4 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)); 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 Color currentColor = null; 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()); if (currentColor == null || !currentColor.equals(c)) { // colours pw.println("" + colorDf.format(r) + " " + colorDf.format(g) + " " + colorDf.format(b) + " rg " + colorDf.format(r) + " " + colorDf.format(g) + " " + colorDf.format(b) + " RG"); currentColor = c; } } else { currentColor = null; } 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 Color currentColor = null; 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 *

* * @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) { BufferedImage bimg = toBufferedImage(img); //TODO: somehow cache these BufferedImage subImg = bimg.getSubimage(sx1, sy1, sx2-sx1, sy2-sy1); return drawImage(subImg, dx1, dy1, dx2-dx1, dy2-dy1, null); } private static BufferedImage toBufferedImage(Image img) { if (img instanceof BufferedImage) { return (BufferedImage) img; } // Create a buffered image with transparency BufferedImage bimage = new BufferedImage(img.getWidth(null), img.getHeight(null), BufferedImage.TYPE_INT_ARGB); // Draw the image on to the buffered image Graphics2D bGr = bimage.createGraphics(); bGr.drawImage(img, 0, 0, null); bGr.dispose(); // Return the buffered image return bimage; } /** * 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()); if (currentColor == null || !currentColor.equals(c)) { // colours pw.println("" + colorDf.format(r) + " " + colorDf.format(g) + " " + colorDf.format(b) + " rg " + colorDf.format(r) + " " + colorDf.format(g) + " " + colorDf.format(b) + " RG"); currentColor = c; } } else { currentColor = null; } 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