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

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

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

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

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

* Draws an oval

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

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

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

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

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

* Not implemented

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

* Draws a filled oval

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

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

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

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

* *

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

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

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

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (page.getPDFDocument().isImageCached(img, interpolate)) { image = page.getPDFDocument().getCachedImage(img, interpolate); //it was previously only part of pattern if (!usedImagesUseMainResources.contains(new ImageInterpolate(img, interpolate))) { page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); usedImagesUseMainResources.add(new ImageInterpolate(img, interpolate)); } } else { PDFMask mask = new PDFMask(img); 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 byte[] jpegImageData = getImageJpegData(img); if (jpegImageData != null) { image = new PDFImage(jpegImageData, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } else { image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R", interpolate); } // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); page.getPDFDocument().cacheImage(img, image); page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); usedImagesUseMainResources.add(new ImageInterpolate(img, interpolate)); } initAlpha(255); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h); AffineTransform transformToSet = newTransform; pw.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

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

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

* Draws an oval

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

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

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

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

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

* Not implemented

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

* Draws a filled oval

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

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

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

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

* *

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

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

* Sets the clipping region to that of a Shape. * * @param s Shape to clip to. */ @Override public void setClip(Shape s) { closeBlock(); if (clip != null) { restoreState(); AffineTransform currentTransform = transform; transform = clipTransform; setTransform(currentTransform); } if (s == null) { clip = null; return; } clipTransform = transform; clip = new Area(s); clipRectangle = s.getBounds(); saveState(); followPath(s, CLIP); pw.println("n"); //setClip(r.x, r.y, r.width, r.height); } /** * Sets the color for drawing * * @param c Color to use */ @Override public void setColor(Color c) { setPaint(c); } /** * @see Graphics2D#setComposite(Composite) */ @Override public void setComposite(Composite comp) { this.composite = comp; } /** * This extension sets the line width to the default of 1mm which is what * Java uses when drawing to a PrintJob. */ public void setDefaultLineWidth() { closeBlock(); // draw any path before we change the line width pw.println("1 w"); } /** * This sets the font. * * @param f java.awt.Font to set to. */ @Override public void setFont(Font f) { // optimize: Save some space if the font is already the current one. if (font != f) { font = f; pdffont = page.getFont("/Type1", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setExistingTtfFont(Font f) { if (font != f) { font = f; pdffont = page.getFont("/TrueType", f.getName(), f.getStyle()); // mark the font as changed newFont = true; } } public void setTtfFont(Font f, File file) throws IOException { if (font != f) { font = f; pdffont = page.getEmbeddedFont(f.getName(), f.getStyle(), file); // mark the font as changed newFont = true; } } private void setLineCap(int cap) { int lineCap = 0; switch (cap) { case BasicStroke.JOIN_MITER: lineCap = 0; break; case BasicStroke.JOIN_ROUND: lineCap = 1; break; case BasicStroke.JOIN_BEVEL: lineCap = 2; break; } if (this.lineCap != lineCap) { closeBlock(); // draw any path before we change the line width this.lineCap = lineCap; pw.println("" + lineCap + " J"); } } private void setLineJoin(int join) { int lineJoin = 0; switch (join) { case BasicStroke.JOIN_MITER: lineJoin = 0; break; case BasicStroke.JOIN_ROUND: lineJoin = 1; break; case BasicStroke.JOIN_BEVEL: lineJoin = 2; break; } if (this.lineJoin != lineJoin) { closeBlock(); // draw any path before we change the line width this.lineJoin = lineJoin; pw.println("" + lineJoin + " j"); } } /** * This extension allows the width of the drawn line to be set * * @param width Line width in pdf graphic units (points) */ public void setLineWidth(float width) { if (width != this.lineWidth) { closeBlock(); // draw any path before we change the line width this.lineWidth = width; pw.println("" + width + " w"); } } private void setMiterLimit(float limit) { if (limit != this.miterLimit) { closeBlock(); // draw any path before we change the line width this.miterLimit = limit; pw.println("" + limit + " M"); } } private void initAlpha(int alpha) { if (currentAlpha != alpha || blendMode != null) { String gsId = "/GSAlpha" + alpha; currentAlpha = alpha; if (!usedAlphas.contains(alpha)) { page.addExtGStateResource(gsId + " <>"); usedAlphas.add(currentAlpha); } pw.println(gsId + " gs"); } } public void setBlendMode(String mode) { if (currentAlpha < 0) { currentAlpha = 255; } String gsName = "/GSBlend" + mode + "Alpha" + currentAlpha; if (!gsBlendModes.contains(gsName)) { page.addExtGStateResource(gsName + " <>"); } pw.println(gsName + " gs"); this.blendMode = mode; } /** * Sets the paint for drawing * * @param paint Paint to use */ @Override public void setPaint(Paint paint) { this.paint = paint; this.shading = null; this.pattern = null; this.paintTransform = null; if (paint instanceof Color) { Color c = (Color) paint; double r = ((double) c.getRed()) / 255.0; double g = ((double) c.getGreen()) / 255.0; double b = ((double) c.getBlue()) / 255.0; closeBlock(); // This ensures any paths are drawn in the previous initAlpha(c.getAlpha()); // colours pw.println("" + r + " " + g + " " + b + " rg " + r + " " + g + " " + b + " RG"); } if (paint instanceof MultipleGradientPaint) { closeBlock(); if ((paint instanceof RadialGradientPaint) || ((paint instanceof LinearGradientPaint) && (((LinearGradientPaint) paint).getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE))) { shading = "gradient"; } else { pattern = "gradient"; } } if (paint instanceof TexturePaint) { closeBlock(); pattern = "texture"; } } private boolean useFunctionShading(MultipleGradientPaint fgrad) { return ((fgrad instanceof RadialGradientPaint) && fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE); } private Color[] convertColorSpace(Color[] colors, MultipleGradientPaint.ColorSpaceType colorSpaceType) { /*if (colorSpaceType == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { Color[] ret = new Color[colors.length]; for (int i = 0; i < colors.length; i++) { int argb = colors[i].getRGB(); int a = argb >>> 24; int r = srgbToLinear[(argb >> 16) & 0xff]; int g = srgbToLinear[(argb >> 8) & 0xff]; int b = srgbToLinear[(argb) & 0xff]; ret[i] = new Color(r, g, b, a); } return ret; }*/ //return colors; return colors; } private String generateRadialFunctionBody(RadialGradientPaint radGrad, boolean alpha) { double a = ((radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) * (radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) * (radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) - radGrad.getRadius() * radGrad.getRadius()); String functionBody = "{\n" + matDf.format(radGrad.getFocusPoint().getX()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getX() - radGrad.getFocusPoint().getX()) + " mul 2 mul\n" //stack size: 3 + matDf.format(radGrad.getFocusPoint().getY()) + " 2 index sub\n" + matDf.format(radGrad.getCenterPoint().getY() - radGrad.getFocusPoint().getY()) + " mul 2 mul\n" + "add\n" //b, stack size: 3 + matDf.format(radGrad.getFocusPoint().getX()) + " 3 index sub\n" + "dup mul\n" + matDf.format(radGrad.getFocusPoint().getY()) + " 3 index sub\n" + "dup mul\n" + "add\n" //c, stack size: 4 + "1 index dup mul 4 " + matDf.format(a) + " mul 2 index mul sub\n" //D, stack size: 4 + "0 index 0 lt\n" + "{\n" + "1\n" + "}" + "{" + "0 index 0 gt\n" + "{\n" + "2 index neg 1 index sqrt add 2 " + matDf.format(a) + " mul div\n" // x1, stack size: 5 + "3 index neg 2 index sqrt sub 2 " + matDf.format(a) + " mul div\n" // x2, stack size: 6 + "0 index 2 index gt{0 index} {1 index} ifelse\n" + "exch pop exch pop\n" //x, stack size 5 + "}" + "{\n" + "2 index neg 2 " + matDf.format(a) + " mul div\n" // x, stack size 5 + "} ifelse\n" + "} ifelse\n" + "exch pop exch pop exch pop exch pop exch pop\n"; //remove index0,1,2,3,4 if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "dup\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.NO_CYCLE) { functionBody += "dup 1 gt {pop 1} if\n"; } else { functionBody += "dup 1 gt {dup floor sub} if\n"; } if (radGrad.getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT) { functionBody += "exch floor 2 mod 1 eq {" + "neg 1 add" + "}\n" + "if\n"; } int num = radGrad.getFractions().length; Color[] rcolors = convertColorSpace(radGrad.getColors(), radGrad.getColorSpace()); for (int i = 0; i < num - 1; i++) { functionBody += "dup " + radGrad.getFractions()[i] + " lt not 1 index " + radGrad.getFractions()[i + 1] + " gt not and\n{\n" + "0 index " + radGrad.getFractions()[i] + " sub " + (radGrad.getFractions()[i + 1] - radGrad.getFractions()[i]) + " div\n"; if (alpha) { functionBody += "0 index " + ((rcolors[i + 1].getAlpha() - rcolors[i].getAlpha()) / 255.0) + " mul " + (rcolors[i].getAlpha() / 255.0) + " add\n"; functionBody += "dup dup\n"; } else { if (radGrad.getColorSpace() == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { functionBody += "0 index " + ((srgbToLinear[rcolors[i + 1].getRed()] - srgbToLinear[rcolors[i].getRed()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getRed()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + ((srgbToLinear[rcolors[i + 1].getGreen()] - srgbToLinear[rcolors[i].getGreen()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getGreen()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + ((srgbToLinear[rcolors[i + 1].getBlue()] - srgbToLinear[rcolors[i].getBlue()]) / 255.0) + " mul " + (srgbToLinear[rcolors[i].getBlue()] / 255.0) + " add\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n"; } else { functionBody += "0 index " + ((rcolors[i + 1].getRed() - rcolors[i].getRed()) / 255.0) + " mul " + (rcolors[i].getRed() / 255.0) + " add\n" + "1 index " + ((rcolors[i + 1].getGreen() - rcolors[i].getGreen()) / 255.0) + " mul " + (rcolors[i].getGreen() / 255.0) + " add\n" + "2 index " + ((rcolors[i + 1].getBlue() - rcolors[i].getBlue()) / 255.0) + " mul " + (rcolors[i].getBlue() / 255.0) + " add\n"; } } if (i < num - 2) { functionBody += "}\n{\n"; } } functionBody += "}if\n"; for (int i = 0; i < num - 2; i++) { functionBody += "}ifelse\n"; } functionBody += "}\n"; return functionBody; } private void initGradientPaint(MultipleGradientPaint grad, Shape fillShape) { if ((grad instanceof LinearGradientPaint) && (((LinearGradientPaint) grad).getCycleMethod() == MultipleGradientPaint.CycleMethod.REFLECT)) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D start = linGrad.getStartPoint(); Point2D end = linGrad.getEndPoint(); double deltaX = end.getX() - start.getX(); double deltaY = end.getY() - start.getY(); Point2D newEnd = new Point2D.Double(end.getX() + deltaX, end.getY() + deltaY); int colorCount = grad.getFractions().length; float fractions2[] = new float[colorCount * 2 - 1]; Color colors2[] = new Color[colorCount * 2 - 1]; float fractionsrev[] = new float[linGrad.getFractions().length]; Color colorsrev[] = new Color[linGrad.getColors().length]; for (int i = 0; i < fractionsrev.length; i++) { colorsrev[i] = linGrad.getColors()[i]; fractionsrev[i] = linGrad.getFractions()[i]; } for (int i = 0; i < colorCount; i++) { colors2[i] = colorsrev[i]; fractions2[i] = fractionsrev[i] / 2; } for (int i = 0; i < colorCount; i++) { colors2[colors2.length - i - 1] = colorsrev[i]; fractions2[colors2.length - i - 1] = 1f - fractionsrev[i] / 2; } LinearGradientPaint linGrad2 = new LinearGradientPaint(start, newEnd, fractions2, colors2, MultipleGradientPaint.CycleMethod.REPEAT); grad = linGrad2; } List functions2Refs = new ArrayList<>(); Color[] colors = convertColorSpace(grad.getColors(), grad.getColorSpace()); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = colors[i - 1]; final Color color2 = colors[i]; final MultipleGradientPaint.ColorSpaceType colorSpace = grad.getColorSpace(); if (colorSpace == MultipleGradientPaint.ColorSpaceType.LINEAR_RGB) { PDFStream function4 = new PDFStream(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4 /Domain [0 1] /Range [0 1 0 1 0 1]".getBytes("UTF-8")); writeStream(os); } }; OutputStream f4Os = function4.getOutputStream(); int redDelta = srgbToLinear[color2.getRed()] - srgbToLinear[color1.getRed()]; int greenDelta = srgbToLinear[color2.getGreen()] - srgbToLinear[color1.getGreen()]; int blueDelta = srgbToLinear[color2.getBlue()] - srgbToLinear[color1.getBlue()]; String functionBody = "{" + "0 index " + redDelta + " mul " + srgbToLinear[color1.getRed()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "1 index " + greenDelta + " mul " + srgbToLinear[color1.getGreen()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "2 index " + blueDelta + " mul " + srgbToLinear[color1.getBlue()] + " add 255 div\n" + "0 index 0.0031308 gt not {12.92 mul}{1 2.4 div exp 1.055 mul 0.055 sub}ifelse\n" + "}"; try { f4Os.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(function4); functions2Refs.add(function4.getSerialID() + " 0 R"); } else { // PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getRed()) / 255.0f) + " " + (((float) color1.getGreen()) / 255.0f) + " " + (((float) color1.getBlue()) / 255.0f) + "] /C1 [" + (((float) color2.getRed()) / 255.0f) + " " + (((float) color2.getGreen()) / 255.0f) + " " + (((float) color2.getBlue()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } } List functions2AlphaRefs = new ArrayList<>(); Color[] alphaColors = grad.getColors(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = alphaColors[i - 1]; final Color color2 = alphaColors[i]; PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + "] /C1 [" + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + "] /N 1\n").getBytes("UTF-8")); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2AlphaRefs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFGradientFunction3(fgrad, functions2Refs); page.getPDFDocument().add(function3); PDFObject function3Alpha = new PDFGradientFunction3(fgrad, functions2AlphaRefs); page.getPDFDocument().add(function3Alpha); double glen = 0; double divisor = 1; double maxlen = 256; if ((fgrad instanceof LinearGradientPaint) && (fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE)) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; Point2D startPoint = new Point2D.Double(); Point2D endPoint = new Point2D.Double(); startPoint = linGrad.getStartPoint(); endPoint = linGrad.getEndPoint(); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); glen = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (glen > maxlen) { divisor = glen / maxlen; glen = maxlen; } } final double flen = glen; PDFStream radialFunction; PDFStream radialAlphaFunction; if (useFunctionShading(fgrad)) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; radialFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; radialAlphaFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes("UTF-8")); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes("UTF-8")); os.write("/Range [0 1 0 1 0 1]\n".getBytes("UTF-8")); //3 - R,G,B writeStream(os); } }; //PDF reference, page 176 /*double b = 2 * (focalX - x) * (centerX - focalX) + 2 * (focalY - y) * (centerY - focalY); double c = (focalX - x) * (focalX - x) + (focalY - y) * (focalY - y); double D = b * b - 4 * a * c;*/ //(-b + Math.sqrt(D)) / (2 * a) //D = b * b - 4 * a * c; String functionBody = generateRadialFunctionBody(radGrad, false); OutputStream funOs = radialFunction.getOutputStream(); try { funOs.write(functionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } String alphaFunctionBody = generateRadialFunctionBody(radGrad, true); funOs = radialAlphaFunction.getOutputStream(); try { funOs.write(alphaFunctionBody.getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(radialFunction); page.getPDFDocument().add(radialAlphaFunction); } else { radialFunction = null; radialAlphaFunction = null; } PDFObject shadingObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3, radialFunction); page.getPDFDocument().add(shadingObj); PDFObject shadingAlphaObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3Alpha, radialAlphaFunction); page.getPDFDocument().add(shadingAlphaObj); shadingCount++; final int fCurrentShadingCount = shadingCount; PDFStream alphaObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Group << /CS /DeviceGray /S /Transparency >>\n".getBytes("UTF-8")); os.write("/Type /XObject\n".getBytes("UTF-8")); os.write("/Resources <<\n".getBytes("UTF-8")); os.write("/Shading <<".getBytes("UTF-8")); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes("UTF-8")); os.write(">>\n".getBytes("UTF-8")); //shading os.write(">>\n".getBytes("UTF-8")); //resources os.write("/Subtype /Form\n".getBytes("UTF-8")); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes("UTF-8")); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(alphaObject); String alphaExtGState = "/GradAlpha" + fCurrentShadingCount + " <<" + "/SMask <<" + "/Type /Mask\n" + "/S /Luminosity\n" + "/G " + alphaObject.getSerialID() + " 0 R" + ">>" + ">>"; if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D startPoint = linGrad.getStartPoint(); Point2D endPoint = linGrad.getEndPoint(); Point2D startPointTrans = new Point2D.Double(); Point2D endPointTrans = new Point2D.Double(); transform.transform(linGrad.getStartPoint(), startPointTrans); transform.transform(linGrad.getEndPoint(), endPointTrans); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); AffineTransform m = new AffineTransform(); if (usePTransform) { m.concatenate(pTransform); } m.concatenate(transform); m.concatenate(AffineTransform.getTranslateInstance(startPoint.getX(), endPoint.getY())); m.concatenate(AffineTransform.getScaleInstance(divisor, divisor)); m.concatenate(AffineTransform.getRotateInstance(-alfa)); String matrixStr = "" + matDf.format(m.getScaleX()) + " " + matDf.format(m.getShearY()) + " " + matDf.format(m.getShearX()) + " " + matDf.format(m.getScaleY()) + " " + matDf.format(m.getTranslateX()) + " " + matDf.format(m.getTranslateY()); PDFStream innerPattern = new PDFStream("/Pattern") { @Override public void write(OutputStream os) throws IOException { writeStart(os); double w; double h; w = 1; h = flen; os.write("/PatternType 1\n".getBytes("UTF-8")); os.write("/PaintType 1\n".getBytes("UTF-8")); os.write("/TilingType 2\n".getBytes("UTF-8")); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes("UTF-8")); os.write(("/XStep " + w + "\n").getBytes("UTF-8")); os.write(("/YStep " + h + "\n").getBytes("UTF-8")); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes("UTF-8")); os.write(("/Matrix [" + matrixStr + "]\n").getBytes("UTF-8")); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes("UTF-8")); patOs.write(("/Shin" + shadingCount + " sh").getBytes("UTF-8")); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(innerPattern); page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; return; } currentAlpha = -1; page.addExtGStateResource(alphaExtGState); pw.println("/GradAlpha" + fCurrentShadingCount + " gs"); page.addShadingResource("/Sh" + shadingCount + " " + shadingObj.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } /** * Hack: * Images, which have method boolean isJpeg() and byte[] getImageData() can be stored as JPEGs * You must implement an Image which has these methods to be properly able to save JPEGS */ private byte[] getImageJpegData(Image img) { byte[] jpegImageData = null; try { Method jpegMethod = img.getClass().getDeclaredMethod("isJpeg"); boolean isJpeg = (boolean) jpegMethod.invoke(img); if (isJpeg) { Method getImageDataMethod = img.getClass().getDeclaredMethod("getImageData"); jpegImageData = (byte[]) getImageDataMethod.invoke(img); } } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { //ignore } return jpegImageData; } private void initTexturePaint(TexturePaint texturePaint) { byte[] jpegImageData = getImageJpegData(texturePaint.getImage()); BufferedImage img = texturePaint.getImage(); PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); Rectangle2D anchorRect = texturePaint.getAnchorRect(); final double w = anchorRect.getWidth(); final double h = anchorRect.getHeight(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image; if (page.getPDFDocument().isImageCached(img, 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 diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java index e8d35e13d..da8980c1e 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java @@ -88,6 +88,10 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { this(img, null, false); } + public boolean isInterpolate() { + return interpolate; + } + /** * Creates a new PDFImage instance. *