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

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

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

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

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

* Draws an oval

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

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

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

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

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

* Not implemented

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

* Draws a filled oval

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

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

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

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

* *

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

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

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