From a32494487d7fb242649a2a0c33153c0e626fef0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Mon, 22 Mar 2021 19:00:15 +0100 Subject: [PATCH] PDF - try to implement objects grouping --- lib/gnujpdf.jar | Bin 197517 -> 201017 bytes .../flash/exporters/DualPdfGraphics2D.java | 12 +++- .../flash/exporters/GraphicsGroupable.java | 14 ++++ .../decompiler/flash/timeline/Timeline.java | 20 +++++- libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- libsrc/gnujpdf/src/gnu/jpdf/PDFPage.java | 67 +++++++++--------- 6 files changed, 79 insertions(+), 36 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/GraphicsGroupable.java diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 933c6dce031995718122e217c0f4db2b4b05d859..41cb871e4a48295c94b10443fa8a6f1919d4b207 100644 GIT binary patch delta 44992 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 t8A9_;xH{qdl|L=M z3rH6na{K7TIFM^Qyqth&?b{};mT{N-QXud&n@CQlz;I1H7EtnYI$Mt@Gz6 zrtE9?WT)K*&P6`O5rYP!Si`*Te;{b<{3#XKb~cxU2ExO!-9W7cIzbvQsFZR|4XXe+ zkUxO~(R9gypp6emB7Z+EmeDeZFE%Rz%U{tMdcd)>j$>y#omVNosGCt;C3FWQbUO*5 zS6iU^?{k^qh%<$V{A$dDplOouTl9W-Di$lLKJk^cD zIZYI~n|zYg4Ze90*wm8579|_b4+nOYSBn!MgQlIDu`TD6QbRg$yMvHprm1;2@p-TH!v9JHsNK({2%IO+NNqHxfKdqcha?)BoF-b zIrU>utM`!;P<3)2r+B+c3^Q{WWTG{7`m{pKG6)>+)MwP{cMS*aBg1od>2vG!(!x%i zYxVj0GOj@x7owXP1yUCm%0~Uw%Iaf6R2#4q*q8BgO8H(8rP`!dz_Eq&Ya!PfC<=~_ z)ulyYFa0|fLZYr`;0VnYm$3&+Nx+-$QkeN6#hV{tc|C^7a~zZB1Z9~gX}WO01Czk@B?rYbT|jmj4YV0${O3+O@Ii zZ(ZMuVjv0R{zhS&S5hZ)agqLcj(J& z^_8{y{neacM?V-CP0di#a2{KuKU}9j0sz0Q=7r%*uNl>@!S+*sRLhJ*@OKmsDUP`K zxZQfyh#mT)aR^u=aBjA>{-i_PTCeYfd+`=<#S~4&*t|+O4*p}~X$8U$ux)EhwrUn7 zs>6R$lU*sV&4Q#(D~NWUL92xOtovVhu7qmWx0LGs&IKRFVut&CR;()v&kJ}?)y}!U zhq<@~NQXa|fV7`~qX-#w9OYLXv`dvJ8 zRQSrr7LC61Ox>PR^t${dSlQROMbt>RiR?OkUQv1Z+t>djeI@p!2p6a79AVvq~1G&MBKQGj-&kT-&`Q;8whlrCH_y*PLFykcmn`)4WcI;Ut6luxkL%Lxs1!+(+vVkrtsep+ad@KwtZ z)^ITFDz%A}pk489Q}1rPh2Racw75zNRj_0qX@bIQFS$iUt0>tCduBXc{vg%^`D=yG zR#{hZOYPcr!mWp|$GyW-WQeEX&+l1kB{o?vbrKVG$cBpNtmd7?`qtHOL$ed^eQGI5 z?1mqSS7{*ZqU4G?8X@-3c(KGUzh z6-fpsayt(^<+Zmq^%j|CB6pN;o#0frDqD-PUVKxSRo_~0PA@n(1gYt$LH9$Yl)Kip zPKT{Y9#ggN$St)?GQ?NH`f#ym<g7HII z56pz~LB!dU(Qq|1W09m*>)GCL2DyBe|&%o6Q# zW$B7eAY9+lHY@|Lpjq4fKt%a!5XD)XjjTDyqY?0y;VFKQg0(G6q@u;2XNkm2S$Rof z1miw8v!FWL>lVFHDO`f%Os+C6rx;c#c2G-G8_q_ z*zj}Qq65oLR@*e|s$-q~+$oY-c~3y<6$3@bR3ART0Ad&%>Be9&kTte5-QvqMUK%?> zrcVZnp_ww1GAYBiAX6;7HN-)@ARf-}5~w%)TQ&knZvcS4wXtyGAQ5+)1faG928+y{ z{l*`HaSClAT;s#Q%>*f0l>NdW5!=48b+`GIBP9TAY#+ifrGvz00?WxkIRk&9oC#_= zFPaPjQAjm!|@I?9(0;%{lQMY*7h^o5sT|{VZ5*m{obXn_XYN7(x`#60!E_)mQ_Z@ulCo+NC+(`)m6TPJJc3;{ zyOMLzbN-cV0mCc-mGn5ZvEJh0(~pTLH4HMWdyw%5F~fmy4Ob>~lH zUFc+8$-3Cd`tVQ@?>EEtVJ5xdm(_obZzy$%ll32gS>Hs~{Xdfx zo^%xEcghp-3ft^?eE?bC{+X;x?fHg*@*^2{Q7c6@<;`>Q!VHaXI_LSObDnQH=lQ1d0N-@(>CBWg$Y{xO(Txz? zdIxj}rZYsK6i3c^5VEHOog?10(ua#x@Mcg$=fLZ>+E2H3j<{f*ED*`wUmzmBhVF7b zOQ|vl1D6-~k}JP+#D8k&y&C#R`e%fLO%;qdVF$KK0GFLenQJ#)ZIT4BZ2)!ywe1|q zI5|@JWn^H|Xc>=+u~6)o8n}n4jkRfn=q8+^qC!4!PCwc?&Veg6* z`VmEXZnqE(?5SEk32{Nz zq2Z!!o;>QXd+&@Bz?qw;N!-@@H<2U62c@}!l8d;|#yD$4fk?sSdx$kJU-Z}I*KliF zzUUPb$&tQf7-e+3{Mf?!vFXRIzK=21;DRPO(|jLet;g`u>jMj5KoZ%C#$h^$%PkUv zz&qu_U+nGRorc1_VIB<v*NS}*IbV^L7Z^Tr(D2nL1@J_=(PUqHQ26qz07|fa6Tg>KcF^7kVxja+M z;}UT%hFbouj zixnbDREk#OKG8v}Qph2_cVZTj=k`aOtHyLV->fRWaL);sYqoZ0@G62x!dA#}Cs4x5t8 zwA;}@O=n<=tb&hnn|2stFE%jV2p=%C#ntsi5NntrK3Yq>WN3W(-Q7f-JMht^>z{yL6phStP-Kum)_c1@eN z5d8T;PVjI`ZiOR)84HRQJFsz>Q#Icc)uQ7#cUB?li9Z8Q7utBhI=8ic-I4n@L(XIm zx91L)`HD#I-^s(BAv;S!GiH~Um6ikY2JYhFt};$5E}0R;-N2{IbDh+p#TBU|OJ|o< z7@W%8J=lDESmis!kx-*-0b;@2m*GSxvO`t&!Km61)nGnj#Vj07X|qbZvk9R`To#g zjO5?Z2r!>Aw3GUA7DCXD7cS0Lb3ld$BE*oK6JRT|uo1Kdg)kWDG|J|`fR*e5+P0WC z@(_fAaHzNzOl21=F6%&a4n@eMJzzQW5DMu_N4NtILogJ4$N@aNU=bOPO5H;6uJsUT zZx#^9RDZBzN3+5}nB;STYRMn}1Tq6H(O(mH7R#1M)b5C3AAuTzTEVV1Qmqt`+?z); zEb@Zo>ds>&*RBeQ!vW$om@uPzgZPfC)Ps#HG`2&B%*){Qn<<7*$SEWKhDi_(cpGc? zy@u@-l5?KDbTA_{HgpHItYHYLX8};fFa!TJe1=XI$H(e2 z9Uf7fi2rwU>l$w7i>%>}APZ9D?7~3d-v_W?%k$N6T1ByRztw(^7~=gFgYzA__B0pCPPz}kDmtwJ*50(TdikP`-ks`{h zX}u`>ib%iS4Ff~d(8gyN&}RBohBxn%7C!7nbL_mZzz`>u&xvE~h{ z*>y2c+;Pk*xGr*BPk+s1Z92*^)(h7~H&JoU`sa0#EkdtbN%bN}OEQF2Trb-7yy2Q5 z0Qz^4f^NJ!c;YcF&RShB;#^O*5L8t(I?~!xk3wd)x6ap#2i!^39Xx}#}2$5iB7b> r5n4o2;2uvnoNWBtTv@%-O3<_c!Z>J6(zG_syGsQcTwe5)lJ@jpVh+t{ delta 43023 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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 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 = new AffineTransform(pTransform); 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(); 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(); 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(); 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 (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"); } } \ 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); 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 diff --git a/libsrc/gnujpdf/src/gnu/jpdf/PDFPage.java b/libsrc/gnujpdf/src/gnu/jpdf/PDFPage.java index 948a79f3d..f2034e4e4 100644 --- a/libsrc/gnujpdf/src/gnu/jpdf/PDFPage.java +++ b/libsrc/gnujpdf/src/gnu/jpdf/PDFPage.java @@ -541,6 +541,41 @@ public class PDFPage extends PDFObject implements Serializable { // os.write(Integer.toString(rotate).getBytes()); // os.write("\n".getBytes()); // } + writeResources(os); + + // The thumbnail + if (thumbnail != null) { + os.write("/Thumb ".getBytes()); + os.write(thumbnail.toString().getBytes()); + os.write("\n".getBytes()); + } + + // the /Contents pages object + if (contents.size() > 0) { + if (contents.size() == 1) { + PDFObject ob = (PDFObject) contents.elementAt(0); + os.write("/Contents ".getBytes()); + os.write(ob.toString().getBytes()); + os.write("\n".getBytes()); + } else { + os.write("/Contents [".getBytes()); + os.write(PDFObject.toArray(contents).getBytes()); + os.write("\n".getBytes()); + } + } + + // The /Annots object + if (annotations.size() > 0) { + os.write("/Annots ".getBytes()); + os.write(PDFObject.toArray(annotations).getBytes()); + os.write("\n".getBytes()); + } + + // finish off with its footer + writeEnd(os); + } + + public void writeResources(OutputStream os) throws IOException { // Now the resources os.write("/Resources << ".getBytes()); // fonts @@ -605,39 +640,7 @@ public class PDFPage extends PDFObject implements Serializable { } os.write(" >> ".getBytes()); } - os.write(">>\n".getBytes()); - - // The thumbnail - if (thumbnail != null) { - os.write("/Thumb ".getBytes()); - os.write(thumbnail.toString().getBytes()); - os.write("\n".getBytes()); - } - - // the /Contents pages object - if (contents.size() > 0) { - if (contents.size() == 1) { - PDFObject ob = (PDFObject) contents.elementAt(0); - os.write("/Contents ".getBytes()); - os.write(ob.toString().getBytes()); - os.write("\n".getBytes()); - } else { - os.write("/Contents [".getBytes()); - os.write(PDFObject.toArray(contents).getBytes()); - os.write("\n".getBytes()); - } - } - - // The /Annots object - if (annotations.size() > 0) { - os.write("/Annots ".getBytes()); - os.write(PDFObject.toArray(annotations).getBytes()); - os.write("\n".getBytes()); - } - - // finish off with its footer - writeEnd(os); } /**