From 4e93bf0af04833e4eb6b0b1a4edcaade85160c1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 28 Mar 2021 17:56:55 +0200 Subject: [PATCH] Fixed: PDF export - Smoothed bitmaps --- CHANGELOG.md | 5 +++-- lib/gnujpdf.jar | Bin 206295 -> 206791 bytes libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java | 17 ++++++++++++----- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f21d8f73..4579a8087 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,8 @@ All notable changes to this project will be documented in this file. - [#1015], [#1466], [#1513] Better error messages during saving, display message on out of memory ### Fixed -- PDF Export - NullPointer when font of text is missing -- PDF Export - Text position on font change +- PDF export - NullPointer when font of text is missing +- PDF export - Text position on font change - Writing DefineFont2/3 ascent/descent as SI16 - it's UI16 - [#1660] Empty thumbnail view on remove item - [#1669] FILLSTYLE color handling in DefineShape3/4 @@ -20,6 +20,7 @@ All notable changes to this project will be documented in this file. - [#1671] JPEG images display when not CMYK - Generic tag editor - remove more items at once - [#1669] Flash viewer - Smoothed vs non-smoothed bitmaps +- PDF export - Smoothed bitmaps ## [14.3.1] - 2021-03-25 ### Fixed diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 2853c02fa39450f6c2c5c9afad2d0ac10806a366..01b277e3eaaa856e1cc21f88d53fc57ae59d3f99 100644 GIT binary patch delta 38353 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%0DdbYyv*0fqBPOd0lFr<9hypfO(xq(t(d&-TT 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 XTlXoptq?@(kIWs}*cyMtE2{0k_;@v5 delta 38338 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 usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * * @see #create */ private boolean child; private Area clip; private AffineTransform clipTransform; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: The last known moveto/lineto x coordinate * * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: The last known moveto/lineto y coordinate * * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; private AffineTransform paintTransform; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: This is written to the stream when the newPath() * is called. np then clears this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties private AffineTransform transform; /** * Part of the optimizer: The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering private String shading = null; private String pattern = null; private Set usedAlphas = new HashSet<>(); private Set gsBlendModes = new HashSet<>(); private int currentAlpha = 255; private String blendMode; private int shadingCount = 0; private int objId = 0; private boolean usePTransform = true; private static int[] srgbToLinear = new int[256]; private static int[] linearToSrgb = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(i); linearToSrgb[i] = convertLinearRGBtoSRGB(i); } } private static int convertSRGBtoLinearRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.04045f) { output = input / 12.92f; } else { output = (float) Math.pow((input + 0.055) / 1.055, 2.4); } return Math.round(output * 255.0f); } private static int convertLinearRGBtoSRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.0031308) { output = input * 12.92f; } else { output = (1.055f * ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f; } return Math.round(output * 255.0f); } /** * @see Graphics2D#addRenderingHints(Map) */ @Override public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc, double ayc, double width, double height, double ang1, double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1 % 360.0) * degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width * cos0; y0 = ayc + height * sin0; // NB: !clockwise here as Java Space is inverted to User Space if (!clockwise) { // Quadrant reduction while (ang1 < ang2) { ang2 -= 360.0; } while ((adiff = ang2 - ang1) < -90.0) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while (ang2 < ang1) { ang2 += 360.0; } while ((adiff = ang2 - ang1) > 90.0) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width * sin0; double yt = y0 + trad * height * cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path Important: We * write directly to the stream here, because this method operates in User * space, rather than Java space. * * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w, double h, double x0, double y0, double x3, double y3, double xt, double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx * dx + dy * dy; double w2 = w * w, h2 = h * h; double r2 = w2 + h2; double fw = 0.0, fh = 0.0; if (dist < (r2 * 1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0; } // The path must have a starting point if (first) { moveto(x0, y0); } double x = x0 + ((xt - x0) * fw); double y = y0 + ((yt - y0) * fh); x0 = x3 + ((xt - x3) * fw); y0 = y3 + ((yt - y3) * fh); // Finally the actual curve. curveto(x, y, x0, y0, x3, y3); } /** * This simply draws a White Rectangle to clear the area * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clearRect(int x, int y, int w, int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x, y, w, h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ @Override public void clip(Shape s) { if (s == null) { setClip(null); return; } Area newClip; if (clip == null) { newClip = new Area(s); } else { newClip = (Area) clip.clone(); newClip.intersect(new Area(s)); } setClip(newClip); } /** * This extra method allows PDF users to clip to a Polygon. * *

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

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

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; 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); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); 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()); 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()); } 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()); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2Refs.add(function2.getSerialID() + " 0 R"); } } List functions2AlphaRefs = new ArrayList<>(); Color[] alphaColors = grad.getColors(); for (int i = 1; i < grad.getColors().length; i++) { final Color color1 = alphaColors[i - 1]; final Color color2 = alphaColors[i]; PDFObject function2 = new PDFObject(null) { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write(("/FunctionType 2 /Domain [0 1] /C0 [" + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + " " + (((float) color1.getAlpha()) / 255.0f) + "] /C1 [" + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + " " + (((float) color2.getAlpha()) / 255.0f) + "] /N 1\n").getBytes()); writeEnd(os); } }; page.getPDFDocument().add(function2); functions2AlphaRefs.add(function2.getSerialID() + " 0 R"); } final MultipleGradientPaint fgrad = grad; PDFObject function3 = new PDFGradientFunction3(fgrad, functions2Refs); page.getPDFDocument().add(function3); PDFObject function3Alpha = new PDFGradientFunction3(fgrad, functions2AlphaRefs); page.getPDFDocument().add(function3Alpha); double glen = 0; double divisor = 1; double maxlen = 256; if ((fgrad instanceof LinearGradientPaint) && (fgrad.getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE)) { LinearGradientPaint linGrad = (LinearGradientPaint) fgrad; Point2D startPoint = new Point2D.Double(); Point2D endPoint = new Point2D.Double(); startPoint = linGrad.getStartPoint(); endPoint = linGrad.getEndPoint(); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); glen = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (glen > maxlen) { divisor = glen / maxlen; glen = maxlen; } } final double flen = glen; PDFStream radialFunction; PDFStream radialAlphaFunction; if (useFunctionShading(fgrad)) { RadialGradientPaint radGrad = (RadialGradientPaint) fgrad; radialFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes()); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes()); os.write("/Range [0 1 0 1 0 1]\n".getBytes()); //3 - R,G,B writeStream(os); } }; radialAlphaFunction = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/FunctionType 4\n".getBytes()); //pdf reference, page 168 os.write("/Domain [-1000000 1000000 -1000000 1000000]\n".getBytes()); os.write("/Range [0 1 0 1 0 1]\n".getBytes()); //3 - R,G,B writeStream(os); } }; //PDF reference, page 176 /*double b = 2 * (focalX - x) * (centerX - focalX) + 2 * (focalY - y) * (centerY - focalY); double c = (focalX - x) * (focalX - x) + (focalY - y) * (focalY - y); double D = b * b - 4 * a * c;*/ //(-b + Math.sqrt(D)) / (2 * a) //D = b * b - 4 * a * c; String functionBody = generateRadialFunctionBody(radGrad, false); OutputStream funOs = radialFunction.getOutputStream(); try { funOs.write(functionBody.getBytes()); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } String alphaFunctionBody = generateRadialFunctionBody(radGrad, true); funOs = radialAlphaFunction.getOutputStream(); try { funOs.write(alphaFunctionBody.getBytes()); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(radialFunction); page.getPDFDocument().add(radialAlphaFunction); } else { radialFunction = null; radialAlphaFunction = null; } PDFObject shadingObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3, radialFunction); page.getPDFDocument().add(shadingObj); PDFObject shadingAlphaObj = new PdfGradientShading(fgrad, flen, useFunctionShading(fgrad), function3Alpha, radialAlphaFunction); page.getPDFDocument().add(shadingAlphaObj); shadingCount++; final int fCurrentShadingCount = shadingCount; PDFStream alphaObject = new PDFStream() { @Override public void write(OutputStream os) throws IOException { writeStart(os); os.write("/Group << /CS /DeviceGray /S /Transparency >>\n".getBytes()); os.write("/Type /XObject\n".getBytes()); os.write("/Resources <<\n".getBytes()); os.write("/Shading <<".getBytes()); os.write(("/ShA" + fCurrentShadingCount + " " + shadingAlphaObj.getSerialID() + " 0 R").getBytes()); os.write(">>\n".getBytes()); //shading os.write(">>\n".getBytes()); //resources os.write("/Subtype /Form\n".getBytes()); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes()); //fixme writeStream(os); } }; OutputStream alphaOs = alphaObject.getOutputStream(); try { alphaOs.write(("/ShA" + fCurrentShadingCount + " sh\n").getBytes()); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(alphaObject); String alphaExtGState = "/GradAlpha" + fCurrentShadingCount + " <<" + "/SMask <<" + "/Type /Mask\n" + "/S /Luminosity\n" + "/G " + alphaObject.getSerialID() + " 0 R" + ">>" + ">>"; if ((grad instanceof LinearGradientPaint) && ((LinearGradientPaint) grad).getCycleMethod() != MultipleGradientPaint.CycleMethod.NO_CYCLE) { LinearGradientPaint linGrad = (LinearGradientPaint) grad; Point2D startPoint = linGrad.getStartPoint(); Point2D endPoint = linGrad.getEndPoint(); Point2D startPointTrans = new Point2D.Double(); Point2D endPointTrans = new Point2D.Double(); transform.transform(linGrad.getStartPoint(), startPointTrans); transform.transform(linGrad.getEndPoint(), endPointTrans); double deltaX = endPoint.getX() - startPoint.getX(); double deltaY = endPoint.getY() - startPoint.getY(); double tana = deltaX / deltaY; double alfa = Math.atan(tana); AffineTransform m = new AffineTransform(); 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()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + w + " " + h + "]\n").getBytes()); os.write(("/XStep " + w + "\n").getBytes()); os.write(("/YStep " + h + "\n").getBytes()); os.write(("/Resources << " + "/Shading << /Shin" + fCurrentShadingCount + " " + shadingObj.getSerialID() + " 0 R >>" + "/ExtGState <<" + alphaExtGState + ">>" + ">>\n").getBytes()); os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); try { patOs.write(("/GradAlpha" + fCurrentShadingCount + " gs").getBytes()); patOs.write(("/Shin" + shadingCount + " sh").getBytes()); } catch (IOException ex) { Logger.getLogger(PDFGraphics.class.getName()).log(Level.SEVERE, null, ex); } page.getPDFDocument().add(innerPattern); page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; return; } currentAlpha = -1; page.addExtGStateResource(alphaExtGState); pw.println("/GradAlpha" + fCurrentShadingCount + " gs"); page.addShadingResource("/Sh" + shadingCount + " " + shadingObj.getSerialID() + " 0 R "); this.shading = "/Sh" + shadingCount; } private void initTexturePaint(TexturePaint texturePaint) { BufferedImage img = texturePaint.getImage(); PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); Rectangle2D anchorRect = texturePaint.getAnchorRect(); final double w = anchorRect.getWidth(); final double h = anchorRect.getHeight(); Object interpolationHint = getRenderingHint(RenderingHints.KEY_INTERPOLATION); boolean interpolate = interpolationHint == RenderingHints.VALUE_INTERPOLATION_BILINEAR || interpolationHint == RenderingHints.VALUE_INTERPOLATION_BICUBIC; PDFImage image = new PDFImage(img, 0, 0, img.getWidth(), img.getHeight(), new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) { return true; } }, "" + mask.getSerialID() + " 0 R", interpolate); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); AffineTransform m = new AffineTransform(); AffineTransform ptt = new AffineTransform(); 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()); os.write("/PaintType 1\n".getBytes()); os.write("/TilingType 2\n".getBytes()); os.write(("/BBox [0 0 " + matDf.format(w) + " " + matDf.format(h) + "]\n").getBytes()); os.write(("/XStep " + matDf.format(w) + "\n").getBytes()); os.write(("/YStep " + matDf.format(h) + "\n").getBytes()); os.write(("/Resources << ").getBytes()); os.write("/XObject << ".getBytes()); os.write((image.getName() + " " + image.getSerialID() + " 0 R").getBytes()); os.write(" >> ".getBytes()); os.write((">>\n").getBytes()); //"1 0 0 1 0 0" os.write(("/Matrix [" + matrixStr + "]\n").getBytes()); writeStream(os); } }; OutputStream patOs = innerPattern.getOutputStream(); PrintWriter patwriter = new PrintWriter(patOs); AffineTransform transformToSet; transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, 0 - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); transformToSet = new AffineTransform(w, 0, 0, -h, w - anchorRect.getX(), 2 * h - anchorRect.getY()); patwriter.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); patwriter.flush(); page.getPDFDocument().add(innerPattern); shadingCount++; page.addPatternResource("/p" + shadingCount + " " + innerPattern.getSerialID() + " 0 R"); this.pattern = "/p" + shadingCount; } /** * Not implemented, as this is not supported in the PDF specification. */ @Override public void setPaintMode() { } /** * Sets a rendering hint * * @param arg0 * @param arg1 */ @Override public void setRenderingHint(Key arg0, Object arg1) { if (arg1 != null) { rhints.put(arg0, arg1); } else { rhints.remove(arg0); } } // Add Graphics2D methods. /** * @see Graphics2D#setRenderingHints(Map) */ @Override public void setRenderingHints(Map hints) { rhints.clear(); rhints.putAll(hints); } /** * @see Graphics2D#setStroke(Stroke) */ @Override public void setStroke(Stroke s) { this.stroke = s; if (stroke instanceof BasicStroke) { BasicStroke bs = (BasicStroke) stroke; setLineCap(bs.getEndCap()); setLineJoin(bs.getLineJoin()); setLineWidth(bs.getLineWidth()); setMiterLimit(bs.getMiterLimit()); // TODO: Line dash pattern } } /** * @see Graphics2D#setTransform(AffineTransform) */ @Override public void setTransform(AffineTransform t) { setNewTranform(new AffineTransform(t)); } /** * Not implemented, as this is not supported in the PDF specification. * * @param c1 Color to xor with */ @Override public void setXORMode(Color c1) { } //============ Text operations ======================= /** * @see Graphics2D#shear(double, double) */ @Override public void shear(double shx, double shy) { AffineTransform newTransform = new AffineTransform(transform); newTransform.shear(shx, shy); setNewTranform(newTransform); } /** * @see Graphics2D#transform(AffineTransform) */ @Override public void transform(AffineTransform tx) { AffineTransform newTransform = new AffineTransform(transform); newTransform.concatenate(tx); setNewTranform(newTransform); } /** * @see Graphics2D#translate(double, double) */ @Override public void translate(double tx, double ty) { AffineTransform newTransform = new AffineTransform(transform); newTransform.translate(tx, ty); setNewTranform(newTransform); } /** * @see Graphics#translate(int, int) */ @Override public void translate(int x, int y) { translate((double) x, (double) y); } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @param tx coordinate * @param ty coordinate * @return String containing the coordinates in PDF text space */ private String twh(float x, float y, float tx, float ty) { float nx = x, ny = y; float ntx = tx, nty = ty; nx = (float) (x - tx); ny = (float) (y - ty); return "" + df.format(nx) + " " + df.format(ny) + " "; } /** * Converts the Java space coordinates into pdf text space. * * @param x coordinate * @param y coordinate * @return String containing the coordinates in PDF text space */ private String txy(float x, float y) { Point2D ptSrc = new Point2D.Float(x, y); Point2D ptDst = new Point2D.Float(); 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()); newPage.writeResources(os); os.write("/Subtype /Form\n".getBytes()); os.write("/BBox [-1000000 -1000000 2000000 2000000]\n".getBytes()); //fixme writeStream(os); } }; page.getPDFDocument().add(retObject); OutputStream os = retObject.getOutputStream(); RawPrintWriter pw2 = new RawPrintWriter(os); page.addXObject("/MyObj" + retObject.getSerialID() + " " + retObject.getSerialID() + " 0 R"); PDFGraphics g = new PDFGraphics(); g.usePTransform = false; g.init(newPage, pw2); g.objId = retObject.getSerialID(); //g.setTransform(new AffineTransform()); return g; } } \ No newline at end of file diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java index de0daa417..a5464988e 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFImage.java @@ -61,6 +61,8 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { private String mask; + private boolean interpolate; + /** * Creates a new PDFImage instance. * @@ -74,14 +76,15 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { * * @param img an Image value */ - public PDFImage(Image img, String mask) { + public PDFImage(Image img, String mask, boolean interpolate) { this(); this.mask = mask; + this.interpolate = interpolate; setImage(img, 0, 0, img.getWidth(this), img.getHeight(this), this); } public PDFImage(Image img) { - this(img, null); + this(img, null, false); } /** @@ -94,16 +97,17 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { * @param h an int value * @param obs an ImageObserver value */ - public PDFImage(Image img, int x, int y, int w, int h, ImageObserver obs, String mask) { + public PDFImage(Image img, int x, int y, int w, int h, ImageObserver obs, String mask, boolean interpolate) { this(); objwidth = w; objheight = h; this.mask = mask; + this.interpolate = interpolate; setImage(img, x, y, img.getWidth(this), img.getHeight(this), obs); } public PDFImage(Image img, int x, int y, int w, int h, ImageObserver obs) { - this(img, x, y, w, h, obs, null); + this(img, x, y, w, h, obs, null, false); } /** @@ -310,7 +314,10 @@ public class PDFImage extends PDFStream implements ImageObserver, Serializable { os.write(Integer.toString(height).getBytes()); os.write("\n/BitsPerComponent 8\n/ColorSpace /DeviceRGB\n".getBytes()); if (mask != null) { - os.write(("\n/SMask " + mask + "\n").getBytes()); + os.write(("/SMask " + mask + "\n").getBytes()); + } + if (interpolate) { + os.write("/Interpolate true\n".getBytes()); } // write the pixels to the stream