From 913405b5636aff3c5c3ed3ca4befe6c7ce48624c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Tue, 23 Mar 2021 12:07:48 +0100 Subject: [PATCH] gnujpdf compression back on --- lib/gnujpdf.jar | Bin 206250 -> 206295 bytes libsrc/gnujpdf/src/gnu/jpdf/PDFGraphics.java | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gnujpdf.jar b/lib/gnujpdf.jar index 3b42e4ef3e394b6fc3f6c7d57f87e553e694a399..2853c02fa39450f6c2c5c9afad2d0ac10806a366 100644 GIT binary patch delta 22233 zcmb`v2Yi%O_CI{jbIVL7PkNoy2?0VTgwUh}q=?c32q*>+h$LVjB$ObQm52>{3;Kv6 zii)fSr6yohz=CZpxNCP;aqWFwU3Y<%|M%W!W-yKZnlf9_vUQve{lM z<9YLe{W;s~o_Ug-Z40d8?c<+YL8j{d{kIAO%zAmHGQfuirn$htcY3=;^&fhQ0_$(vg?B^LfyZPk9LHgJBa}Uodh~a(t0Gkiw<;`=J zr^V2osT0U0p+?v&iJ#iM^~@Zv4^>Cne3UaGm;A%V z*z6aU?B(N#S#<#~ufTIrAchG((dLubAYy9q(wf=zi@kg@is}Pmf6id$L-`ptKa)>$ zHsw;Fypot)5;$e5X*k2?Gg%Oo8!ym!X4!nUn>^pktBCQy)J3}eT$@+(8i(?zAa6dh zr&b4acCF19@P$Y>t~i%iX19?eM>a2+m6AJ1<630%dUggePkqgbkqz~YKE9Z?c=!@$ zaUNyVF0*+ftwrhln#L)$7uIb!uD9_Q!V`~rR<_*vS}Sk+jAl5VFKpQ%fJ zr%NtINw-r^MV9WU$>x_>Gl<2_t*c!;b!k=ovUv?l4cV94{4(cN z?%^bx6Su}RKei^>@8!1=iz%DBba{=&q^mnNzn!mj_T2wskZfS^;Ig?@0X3}QBc}g!?~m)= z+nhPAR(`IRKM7R`l#Lh;UD^W*h78f<@3Z;SY@<^(&dO+tcd%wOi^ zFSz8Kq6zf!mw>}i=H-7tf$sE_ia?o{ziN=rtqS<8NBQeEKcMBRAlJz?)jI2sHvdyt z)T5Tx)YR+Dw`~4)SS?PeD_vfrGvBrOd*Pa)u0GJpvUENO|BEJT@+c4g$e~VDKp#8f z*IR)Yevp4+^H2F_&iGDrYWx@cOAr6b=3nz~hy|UhAO)QlI?<5WPkj7C{=LnA;D2+H z3aKpq@BAkZ|JmmMpr?tYIlb<-(wfj2=srtV{in_U#s3CnL&nI4xyzSLYN*w^{da!Y z=0|iJUA0nI{nh421;bEko~x>BmoEdU-HJzgg#}?>exQYbh1UE=uFJk951b!+gRIB4^Bk4;u#~srs!&mBGJt$?n<4#ABvzYNm=;_?omFfz5yWIr5qA#(;KwV>?zM(PDE5wdh^mBR^QTCJpT4^8&)GOCub?tee zY%mn7XQb*7U7$te6~i=GRb7obr=X6Oh~_0moGMQ9h>^A!B}O~zi)dN)SX+!^&x4C) zOXr_6wPAd1eNEL;uYd`i;e6MPrZ`RA$R{R7k{=;Sr#oMFqqCZ(=wZ^mw37?F=4ORQ z&nu>Z!;5`-SYcts#kKPQ!76zH)zmGkLF#M_`^7#VKPYC~Voq3#rZiS9Z47DAL2-^P z=IWAINc*(up}FdETU;ToY~HXr z#THkIt3BcxTU=|EH7|KLpL@jh%~h|Z2Yup3_yutjv4jOx=T()}Rn^Zg8&OxaY?)8o zBAPv7jV*2!x52+SRi%{DJamP0e&|j^<8K#hJ>m{q+$q)(%g}`x&2=lf#-P=DTih+~ zX})tsh4XqTsjU0>vmOB>8*h48-dI~#HokTl(<=s`Sj-3 z_9i#KzCtxW{3`sgc$gfIc%*s5*fbFSsBV=VvswJ!BeoLDZGNF8*XI*k#CDBhNApMD z1e*hGcBUG(*jQfX6V;G36Job59v5v+RvDd4PrB0X>l06iJ-XrE=7t|~fc|Myka&_$ z>=S!*tLJR7Up((TT}Efni%!K|RvvnLLA~sNmNhJ2I=99v zj=}Q}&JG1-Jw5D8QDMnRm8_Fgvg@c*4v=$QZ$s9gH zp@7}1hPledkdj5V?8cJ8<-EG(7;IKXoA4z|Y}uVIg^^fVv#8;`8lBa{mSv{q+&Y*< zpLLP!)nv=w+Tm)?1tT<~YB97>_O<0nv>Q6Jtfo=hPo0h0{cSlwSJoCwXOD%+($&jt zIY?KJ4OKV!sDH98hiJE`{fVwWt+u)m1PVFKmcu2?xV_BTaT2AgQ*C*gV9x6&QEH!2 zwj8a?;()9|i(-83qS{6+fU&k5$8G_m%g)v0BZMmS$O*)TmW1s*uH!6OkAXEdL_Cq1?&qI+3u%LQ_wvt@x*;`7OQa*-a| z`sTZz?eEjYi*30?E^Vfz-K1;`H|*t;%j9{w;tCku9y;`w7ufPbCvgC!+ZThu!XAZ% z;H}9ydY{$FmaF8Y9(kE9FPB$1a|Tdf?@GB^H^0hRH-K`~H5$IwIgn53+Kf5x4WPV) z>%l@#9|h!%w!DdUJFnbtWsmSkh%c?(GES?hS~$9D*|`-MUwJc2Kv*howdHNbebyPr zIkjO5+^8P3+ikg4-r>{?q|R!cE$;$+<3P%$yPNk8_qRSfkiPa%iL+%mVzZ*wKMtqS zM8jHhM$kAxs~q10R%z?4qv#%~=XtjCk)N`isTDMI#Cq0?WyyQw21fE;sI|qGB7Gk# z45#J&NNJKEy2R5o@!Csf?Xr5bj_*^^;%Xbecf(souF} z0u|9DXWIlSi@_?$6wY+Mnn1ZBEDwW>(d;yk;9`9Q>2c^%W7*^cCsMv2F$R)8Ei61& zL(P*|G8$}l&Y4In=~3t4L>iI(7|LvvF}6rAcOZ{g5%apGQZwXZ))AJ5Pm%>XFdhc$Coa}5r&E3eYwG_MYX-0; zSisl9`ar0go$F7htcj1hoiav4n3sl1O?0{;H73LyV==Oo&PT&tD03M{j@*qLSCWr2 zg4G|aNp1bd>2xhY0+&vq)+Qh$#j@CRHc7TY8}iviHcLJMD4x}^;qpm9scbnLAfE!1 z&i)vw_i3c`yGSXc3&SJ<+JhDt13-HLVV8g|~EbAWWeTESj6V$+KAvV0Xtgn=}r%0QY@?4_XeH9*){0rWZ`+dzK+6mK9+KqB+7 zXXODv$;f?Dz5yt8GFaIn{|GRh1=z##Pk=I^llRLv0cEis>@N8hpd8HTJLKDd@>pNy z$aeq*STh^IeSkW#dsqqi0d)qKS5Ko7&%5kFtq)GyH0l9fznVtbBQ&pl^#HNl2(P)^ zr>DKnBR?R`Yp!eqgdqj=Dj@A|cCf7m-{`Yw9Pq6Aq!LCJ=g=?wmgEloDRzO z(Grf-Ee%aD?n^$x(mkvN_&$(-Hdwut)E8J!uB7ZP?K7Y-f~!#eMScieBtHuAbup?o zMJA7xz+$XKGkIJFeUYZV2!p-|gT4rZz7T_qt;MiFU|``SFro$G2`2xgg*~DTj3wK< zN@vQ-FkNN1lhG6DYc1Po*y%AH^YKw+0ITdQZFbEvO%b+EskJTVm#oF2@IP4 z=j*=lq4u7YCde9OdL8W+YGlsIiz&M_JXjRrh2#MYn;vMAZvaAb`B&>>jo&GqL4815 z)eOpr5_P8#ZAox@pTMRfG-cni?@WukW>7()=09HRgf*LatvM{-sxs7Q1It*Vq2}NW z%1+R&IojW9-Dz6?I*WQagJ)v%>rrRM%wx20NT^fBhR9E)F(-gN1Eh6ti2NLoW@3o^ z0+5FRDZ9-e-93}0o6%3#6|5_nuvW8j>nb+fx|)r)u89)Vm`EpMBAtwhbTTH=$(T?l z4CX{nW|%BMdNRkFj?HvJ-g@+?i5l$enoXUZma{2$wib>{$y!iy2kQae`&#SRAnPte ziK~-?Ba{r5pJG(Bjt!Qd0n!s^u>2g5W_+;x0+60`x4Jsv^qxhP5Z`sPD0`aLCszeV zNAQh~;2RymH#&lEbO;~X$=85cQ$Je14oK_8m+j*;5q(UU{r_!<ld`p9h9+DDy3 z$Kw0H%D|#x(mr#u6Re_B%#gct;d3L{fDu@)h#-0yd#wfA%h+p#}0_$49 zF~RKBIjdW#XbTeV)OL~n7=<+2{puN}ktW5MDP{pHotUhDM`4H}%UEecma)>BEcz|7 z*wGGTac>mTD6%4k&H1{De4ejd1Lhp5qQMaSz;me3csJKbmAEo8CI?bTNCC(6nQPR+ z*-!{ZYoRAtf?(sMcFTi+h%w%qDc;9W;l?KICeIVH@+7~O--P6RHV|q5NCoY9jDYz# zgJXzWswvVIEX$eMNNEY*!skj5^_P&+m(Qgk;Nyb1lyCTOmA*7G)};~tOCyH8G-B9G zBgU{)eiu^uQu%#I>3@)a>qu)yQkO)LI)Wm-lKfHr-6#XepMXsZjpWaOv}Tc^Nn1oR zG=U%a5BZDk%b8wHfzpT>?*CGwCwZ0IYIN94bqNj313zamSTq4zR6!ty;VMWeLB6JsnI zXre9gi2gRt&KVXnHaYPOTJY(lo>VIrFF}3e%P-;iX3~r5j9-&ZDeE zgTTm-<{AX=%%f@0s-E+y$gkx$(-``Zk+vgE+r{%KyT6|Gnu?J|m9*6ysWfF;wMQDn z9t4iPDHVB;D`=djyJ&rSJ{I)oV`eSQHkx}O;=w$|l~f8|tc1-~9_7_>BV85l7f?u~ zbd}K|=oMXMbO?Hd(hxC#bVLSX=qf{kV&`HT#l`$7yHCYnIK1pjcDM2YifJ;V9>c7H zBdpFtmzm*#Lo>sJ#f^znE#6_+MUIiHrdz9nyU5Z|`>@*~-M3U+NcSxj9}<|Q5?mUY z)ua*uYKd7Y36PF!ER_sMhs~Bs0i->xWhAQuXjhLwBJ?OVMCeT*6blC|6gv{y&k(xU zEj7H0u*WkkIKa2Tf3?R=4TD>c~H)ovbB5xhx8uH^Q$sg3cR3 z=MAG{zYlRaX%Tgb;_?!fC{{;tnWWJNx*x+}mNSgg3wpmswCJ3^@~52-xExE-=-MBmd7E^ayq9mZsEO6qANruKn)nqthy zhDxQ6*wn1Povu2oE>QRJY>euviZG^E zGh1rotd)Cxgp~ClrJEsTeTWo`ty2>9cw`U2#a4{Hft}921{@v*$zxd_)!q5FfpYqm z{@2xn*lonnaWzpLmT=T+LiGT5+WwI$0|X;(4N*NqQXhkSZ6!OdR>bpB3K8W$RWH@s zc>?Q}nSK5nhClr_1|uEU+ZqoXvXo?%TY6!&+3B-{uE0R;UP65mw4HYyY@^Yn!%L`3 zR$p);!r@Sl5||omh&9x-EniAmQR?^w^NKH{NHHOpQ@)G}JwvSLOpCjgQg&2}pIMCf zM^p>58g6$ofOZ>~-E~B}z>GbTi&&z(Ad0efQx`GlVN+*Wr-k-lUOkVtfwEOAsDE!wnLA_~ zB9v`#V{$`#AMVB4N&lD23-W8o&$Hv1~7gu!~V7}X;R+q6*R5LT!dP<0x6`*@<*I~|D{5QQc} z6fGpfk+b?@DsIva+{{}wBC2vIA`fYgm3oTDdKBG`RHKgX*4o~&TX%)mK^E3-3wzW? zkiA2)@CJ7{Wh*JRsGW!@QAC6z2+}vNbW^8lCG{8(iDJ~*kk%|L5avWR3hio!g_Q}D z{<-yqY5n|4(&qx+CMulT?heYrn#9ViC#|PInwJ%+(P|7dDUD^p5nj5+vwStdY%u4rBsCEb zb~w}|b-F=xU@xUtw+rR!DE=bDs3xl^y7IA#NlQ8oM)QdpTYtBHGCf^*31vm;z&ca; zxU&Z})j|*i0?$xq9^WVH+p*94Pt>QHrp`LPv9)4Lcd!t(-c+sy>7YKUQcX7$4_!sM zQL@<))m1oErOrId~nhbu3oc|iB=rBr52nk(fqLg>7#LY*TQ zqGP=wY?kPL56_3``bEtR@p2zhI=CGkEZNC;kng0@Hr}_gmG`UYE?Rj1k`}rK0q;%I zO1ii3LBTe*p%TE5R({G(c1N(Jg`c*Qy%TKZqifn-P!JI)Q`j0%zcL>#A0&GO%kW4R z@F>>B<6(V0J~r4B$A)?0jj&ub9v%_GaKyKu7W+Sxnz2<+~LnC^h-Z7p1bUBp+McEYw z#keade?U8m(<1bq7TGa6&D}8yTZ#c-;%0V>!DYx9Z^#*^r=i)`^^9b~Gn#ewjA22~ zSk}|ynmL!7|41XdIYeXGm7sBan8quwr2K*ZJB>Ib$jk>ZheLaOf4Pzh22I${Qs7AL z2=3q$TG%~7+Q}#AbP7LRC;3kHQBdq;Uj&0Ge9H8ll(>`P^nk$WIOVIUPgmW)JJrUz zGBWbO?qF;#*5p=}m2D)_yqXHqr%rQGpQTX;W2f)r_&?iuX*I1YnA1YMg<@LRr@BrX zJ5t%oehn6OZsq5oB!$m&R$oQAUCsuf(_8tuZeF;}J;6dC3U&rUl-D^gUWKr)LD!)! z_=dl2YvD^<2y88HZxhtr*JDi z^17KAI;xC=Gt#R-9i?{-cgMO$g2b+oAhByykcgd`BP@;%u?*}T;?k}bI1p5UYKJt{5 z#hN`L1kCy>aHzkF%uL34pXtx-uH57I6cuO679M|5(C=yQ)k(URGERJ$80rLw%#OD< zSU6Th$67v#rehB}E)LDLdv;O2bIr9BXC&;h@->U|e8W;b-?A*vcZL-=@Xn5~lC91) ztT2|XY5{TJjyJe<(6)dUhnN}ygQV@Ug2}WVY8RP_ygffN>2cR+FvfYhTBHLdw_{6B z$81~LgZC_F!gb`$*CxRr@G6$*^+bjrhXcC3*`+uxbk~S*rUt+A;C0lw`^ufx%pk1u zs*>k%7C}4NwyioA~H&nT1D!ffa)(h5)MxK}4 zK!NOQ;oh#t=Y|%3^G+)dCV|&!blT9h-9Xtrv`KJDDUL|1*m_V$d|?;R4Kyu|M!M!d zART(+|BiHtS_&hx*}3XQD#$ccV&re@Naj9ZG^sw?t1k58@N|QaCbc+ZqoPG4DL^ErrxRRQ}~AC z&xzE|VZwGGN=EM{DC7@@)UT&S))o%EpZ zZlRa?OH$j7jS(4cR2S+YGBUJrGUWx9!yKG#{1LVS%)tlYg%+bGLvKY){+bzpv6z`}7-*mb z_@)bbf-TI^=K7shY5_B05>qG(L>B8&Gm(n+lwr_g#`69DQvPR@o4Bd-rzQKW>b0zo z?y6)T|L_i$fycqMEURQMKiITR|FyUf^U3wnEjx>#y& zcoM6}>7n+y!`NJF93Xw_@@+&o-(u^rF*M#W47ra{2`x#63ej4RMqGOMhSPZ)gCYhBs45miE z+hF8hXw} zg2((0$%StEkXw8xRIHOtsD5bv!npwdtv?H{pFcJ{tp#C}wy_wu)sHQdk93N_z=d+7 zdkmM#NU6pg&ON40gbb7x8r0Cbi>2h zo9GeE;Wjbi0ruIkb##~8#F$90uk{F}(8Fszs@*=CU>{7^(Pi!3kMDrvjVK&%gmD~? z;*B2dC?<43@n#f?H+ysq(Q!PEH=}S&at7T={-$Gu_*zJauXVr>&Tgk*GAM{h;f+xF z8z*k&+FETY#nk9#Z=zX|9!=e=cc!nS zoaC86Ym~$<_5P1`h*zznHe)_<@=yD%O=i*N6|1pS)5|yb6`Wj!PQ1nQ>N0h?wmX0> zG#ho}#P8Gia>M2x^Oh2IVnS()m{l(DT~#hIZTXLWZ1?;) zf1uKdLppJ2{y=Y=sID}nnQE>n$n?%_74yn{P}p;y>KB_9E6)|xPpER+g`Tt??OPrP zthG-eGakT#r;t~k!22bpCHmu^x|bFB6VeiOL0V$(DgJl^*E%<>r+G~WEB(Un^V|No zZDL`os4q|SCys9uODbE%vI*J}G~%;dTLK~a$uw<>8lSqC7Wze+6xF@Ul?Hm3dvvXG zuivX@6xnU!yviW8iu223{0Mi3l>5v)(Gy4m%FLvGvD-uw<{3P7I2UuHpB<{|Dm<#b ziOjgr98D1yK@ZYm^(I1O{uy*8=VD!GcPxCPhskNug>lCg)@$@8YgI_U;vtRr7I9gN zSiOm*mnS5P7IDKaaq~9ekUybC+@)hA?9hQPY|-IB#B7luB@pZ0kwSO5cy^0>5!G#C zL)yefh{CV1>=v7xIrmWZ)O7m!edo|Fz zJf=--)_A@Cm{#$a^U6JN3@5_tp-o3Om6Uth#Fk3Gr?gdUE60Al%Yj#O;1>9$E{O@3 z7~bLs`Vt1(sbc5Ac)!PlFiZ7dxzWRXzXvK^o~X68RkW5TwTbR7lu(oS0yHg|juspR*JW&~J6;G8Xw~2ikdZs)j=!cmX&z7g@ zc0(D+k@uAkvg3D-wp^rR9@^yG(My}=*KRKnPAa!h%6?|&_-4H#rg1lO3Mllc*!vm#zeZy ziQhpqRFQ#aIo{tLh38i|(N7fDsTVwn zo)qj72pzCn7vcspiM>F;|>$qyR26T@{?Cx zqprmYBcCjFow^>o4_(+1bps&Ddb97K!VKDwHHsP59JX+3*Mtp{Y&cD7U94JgjaVdIULjtAr2)jbLqyKyhM zQr)XB^5KkUvbxWLC-%^E8n5oh(!z^v`lW_rX&u0sj;`pn-eH4LaWmdB_?(p*G(PMe z%{28qd^tPYNG6f5WK-2faGbi38@GWZW{wR#XzNalyw#p)qIDKa^P5TR2D zDv`@A+mMwde`iTUR<^v1el=v}$m{55^)Lv`l})$OH|i09d2%o9GqmQ*S7?W!wLt!f zHml8`x=_AP52!~0b&;RYUFtFHKo+To*xPCg$m*u{vAt?5h$&Vtu-$4Khz{z=CLBz8 zj;if?8L0Er4!jzItJ&%;wNte;A)4y1UNCDsVeOP-RjX=&VzMBtozlo z;Mi|2V_{!0#K&`thlqd9y=!1S&vK4pQYCnBdSq35tkoWV(8K?Zf6noRcyKsELXR{L zC;1Va$=|1A#G`ogBC3%vnK*{m|Kd~+@9Pm);R3nY$nc2kl8@p&p_ukgI@D)4k}E&N z81x2vLWsr8=HKNldxGB16Uvl=_i0(9=ejRd(mRdy7oQC#6lM zR*GP&Om|`)quQj8{jp((?(@fLcYWDobmb`-uDh0*9)FDAZk7J>IDedW*70pJr_vwa zD)Y({^x;#cjE~Rstccg0<-a^}@(bR8bWohL>HPF59W|sN>^+xc?Xjv^#j?u?L3_CT`kJUz>I4?JPkLLfA}- z{c69$&K8qmAMRTp#$H#v*kx{va~2Q}z)rph$6F-sHt)Jf)*U z19Zz*)oZRovIc|}dWSu``+k-PHSCMK46+#OA=@NcWN=zodRuo=hLN7TQy6_X=-yY+ zhl8(M--LF4aU7Q9MPCQpS8vfrc!$&U(;`dV9@wH#2dH6oHe-`KETSzM6|1sgFt?M5!fl?TPp1=BBHI~Rz4kDm&w^TW!{^;g1j-yo^H08BYp4m_}$CR<% znH^giMSNTYN1XZ-6hj}zy%{+rJV{fHPshQFbuZex{JVpPT4?%~ds3=@MIbVYk~y1p zQAUCs6T}iRa$4;iY^4ly>EZYj-M8fiqcqv-9YDvN=vuQvjP=?@Ia59kc5lZB3e2H# z>=-aPmLNxWF>@azJW=HaP zXZL~!Xc>nvSnIF*ep!ng-#!D}u{fJ~V`oK?cl^s-DQNB9O~K0!_;oNdqUY_VStWY2 z-P}QlUBFcALKYWW$C6_gvCP!zhXjN7)KP0*;JqSCm{ zK`!Fs3BZ!BrP3^%wH&v#$m#eG9s9c(XFDf7L92^rg;s(3M33Vo@P2HdJOk&nvse|L zTk#h4ZanqI=BH24eMWufK~jUvBH%*|-rNwR(70NCgh?RKe6TtQNV2v_y{%YYd~8Y? ztqR)+S{1SpanxvTSXeBb78>usjdz&NVve$bxJm)<0A~<14ng|_^2jp~m!>imDhii` z3!Pq1QEt;2$%n9pw5+8O_c0LmN4pP%5xB}E=Nxl2z@0`fQQsy)hA}=ef<=5%hs2ea`duaR!0qnyPMo&Pm~`@U zT?&(*;PPn1RO8f3eaf=k2}U^>4gGf1nl?GF5|=<2-yzR!m38AwT)1JUY-p3qDuX4h za(RW$3*Ym@f#i@0`kG&9KV0%N(=mLwoFX;JP)~PrQEyg65}}>O7J0sNXb{s zx|`#0)lT7F+;-9js_qs3H6g(!vdh(HFb#yUYEYkJJ!C-fujp3_bPHEdIn3q36EP^A$Wxb-@pAhSa}jfj^lM zdR748KkkUs+(-QsLo2`j`t$pTqIa~i!Zhci{^7gH>ru4GjpVONDs&(fW z0M7OdKG1q^PV09WT$N_d=aJbNCQb_CXHMw)X6Ap7eQ! zlbFj>1Lk){?L(7CLF@G=$EC=8-@j_w%ckaxTpa#u)Jt;F{G9_Pb7MHOO=tFg$7Ftw zOs~cnJAQqcvp1Kg(|o5OkC&L{6Z24W-AAV8WqBc-YjtM%=g7<}nKX{9R5%*wHq%(v z|2{b$Z?9st&6#3!O=NT?ewEx||s{3KoOm`m2=RNtp zr^(s+G-W!wI`KqD26z@->I4GV%Xsli{8HiHzQixgtqkyQ=N+o_-L^|&0@_}lT(}z3ec?CiR;9R3N$Nws@E;M3If(2eZ4LG zcmJxMeJv-@*UHz}?0jAK=&+IF-Y#NHf6~Jh@w|W4PtM()K)}&+Ea&@ktnAwgarHQ_ z)^g%&tz2iUP9w4h+K&O&!w;Z}1Kb;ro%xyCwWu198Ke*0v z4y>~}ISrjr>5_*nXYs>Up7Xd)&Dmx-leSr1owzPYeYC@JUf*G*Iup8ZTdU#BE<6V} PoG$3XOVk?~I=uOR#v*Ac delta 22412 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 usedImages = new WeakHashMap(); private Color background; /** * This is true for any Graphics instance that didn't create the stream. * * @see #create */ private boolean child; private Area clip; private AffineTransform clipTransform; /** * This holds the current clipRectangle */ protected Rectangle clipRectangle; private Composite composite; private Graphics2D dg2 = new BufferedImage(2, 2, BufferedImage.TYPE_INT_RGB).createGraphics(); /** * This is the current font (in Java format) */ private Font font; /** * Part of the optimizer: When true, we are drawing a path. */ private boolean inStroke; /** * Part of the optimizer: When true, we are within a Text Block. */ private boolean inText; // true if within a Text Block - see newTextBlock() /** * The stroke line cap code; */ private int lineCap = 0; /** * The stroke line join code */ private int lineJoin = 0; /** * The stroke line width */ private float lineWidth = 1.0f; /** * Part of the optimizer: The last known moveto/lineto x coordinate * * @see #moveto * @see #lineto */ private float lx; // last known moveto/lineto coordinates /** * Part of the optimizer: The last known moveto/lineto y coordinate * * @see #moveto * @see #lineto */ private float ly; // last known moveto/lineto coordinates private float miterLimit = 10.0f; /** * Part of the optimizer: When true, the font has changed. */ private boolean newFont; // true if the font changes - see newTextBlock() /** * This is a reference to the PDFPage we are rendering to. */ private PDFPage page; /** * This is the current pen/fill color */ private Paint paint; private AffineTransform paintTransform; /** * This is the current font (in PDF format) */ private PDFFont pdffont; /** * Part of the optimizer: This is written to the stream when the newPath() * is called. np then clears this value. */ private String pre_np; // PDF space transform private AffineTransform pTransform; /** * This is the PrintWriter used to write PDF drawing commands to the Stream */ private RawPrintWriter pw; /** * RenderingHints */ private RenderingHints rhints = new RenderingHints(null); private Stroke stroke; // Start of Graphics2D properties private AffineTransform transform; /** * Part of the optimizer: The last x coordinate when rendering text */ private float tx; // the last coordinate for text rendering /** * Part of the optimizer: The last y coordinate when rendering text */ private float ty; // the last coordinate for text rendering private String shading = null; private String pattern = null; private Set usedAlphas = new HashSet<>(); private Set gsBlendModes = new HashSet<>(); private int currentAlpha = 255; private String blendMode; private int shadingCount = 0; private int objId = 0; private boolean usePTransform = true; private static int[] srgbToLinear = new int[256]; private static int[] linearToSrgb = new int[256]; static { for (int i = 0; i < 256; i++) { srgbToLinear[i] = convertSRGBtoLinearRGB(i); linearToSrgb[i] = convertLinearRGBtoSRGB(i); } } private static int convertSRGBtoLinearRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.04045f) { output = input / 12.92f; } else { output = (float) Math.pow((input + 0.055) / 1.055, 2.4); } return Math.round(output * 255.0f); } private static int convertLinearRGBtoSRGB(int color) { float input, output; input = color / 255.0f; if (input <= 0.0031308) { output = input * 12.92f; } else { output = (1.055f * ((float) Math.pow(input, (1.0 / 2.4)))) - 0.055f; } return Math.round(output * 255.0f); } /** * @see Graphics2D#addRenderingHints(Map) */ @Override public void addRenderingHints(Map hints) { rhints.putAll(hints); } /** * This produces an arc by breaking it down into one or more Bezier curves. * It is used internally to implement the drawArc and fillArc methods. * * @param axc X coordinate of arc centre * @param ayc Y coordinate of arc centre * @param width of bounding rectangle * @param height of bounding rectangle * @param ang1 Start angle * @param ang2 End angle * @param clockwise true to draw clockwise, false anti-clockwise */ public void arc(double axc, double ayc, double width, double height, double ang1, double ang2, boolean clockwise) { double adiff; double x0, y0; double x3r, y3r; boolean first = true; // may not need this //if( ar < 0 ) { //ang1 += fixed_180; //ang2 += fixed_180; //ar = - ar; //} double ang1r = (ang1 % 360.0) * degrees_to_radians; double sin0 = Math.sin(ang1r); double cos0 = Math.cos(ang1r); x0 = axc + width * cos0; y0 = ayc + height * sin0; // NB: !clockwise here as Java Space is inverted to User Space if (!clockwise) { // Quadrant reduction while (ang1 < ang2) { ang2 -= 360.0; } while ((adiff = ang2 - ang1) < -90.0) { double w = sin0; sin0 = -cos0; cos0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 -= 90.0; first = false; } } else { // Quadrant reduction while (ang2 < ang1) { ang2 += 360.0; } while ((adiff = ang2 - ang1) > 90.0) { double w = cos0; cos0 = -sin0; sin0 = w; x3r = axc + width * cos0; y3r = ayc + height * sin0; arc_add(first, width, height, x0, y0, x3r, y3r, (x0 + width * cos0), (y0 + height * sin0) ); x0 = x3r; y0 = y3r; ang1 += 90.0; first = false; } } // Compute the intersection of the tangents. // We know that -fixed_90 <= adiff <= fixed_90. double trad = Math.tan(adiff * (degrees_to_radians / 2)); double ang2r = ang2 * degrees_to_radians; double xt = x0 - trad * width * sin0; double yt = y0 + trad * height * cos0; arc_add(first, width, height, x0, y0, (axc + width * Math.cos(ang2r)), (ayc + height * Math.sin(ang2r)), xt, yt); } /** * Used by the arc method to actually add an arc to the path Important: We * write directly to the stream here, because this method operates in User * space, rather than Java space. * * @param first true if the first arc * @param w width * @param h height * @param x0 coordinate * @param y0 coordinate * @param x3 coordinate * @param y3 coordinate * @param xt coordinate * @param yt coordinate */ private void arc_add(boolean first, double w, double h, double x0, double y0, double x3, double y3, double xt, double yt) { double dx = xt - x0, dy = yt - y0; double dist = dx * dx + dy * dy; double w2 = w * w, h2 = h * h; double r2 = w2 + h2; double fw = 0.0, fh = 0.0; if (dist < (r2 * 1.0e8)) { // JM fw = (w2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / w2))) : 0.0; fh = (h2 != 0.0) ? ((4.0 / 3.0) / (1 + Math.sqrt(1 + dist / h2))) : 0.0; } // The path must have a starting point if (first) { moveto(x0, y0); } double x = x0 + ((xt - x0) * fw); double y = y0 + ((yt - y0) * fh); x0 = x3 + ((xt - x3) * fw); y0 = y3 + ((yt - y3) * fh); // Finally the actual curve. curveto(x, y, x0, y0, x3, y3); } /** * This simply draws a White Rectangle to clear the area * * @param x coordinate * @param y coordinate * @param w width * @param h height */ @Override public void clearRect(int x, int y, int w, int h) { closeBlock(); pw.print("q 1 1 1 RG ");// save state, set colour to White drawRect(x, y, w, h); closeBlock("B Q"); // close fill & stroke, then restore state } /** * @see Graphics2D#clip(Shape) */ @Override public void clip(Shape s) { if (s == null) { setClip(null); return; } Area newClip; if (clip == null) { newClip = new Area(s); } else { newClip = (Area) clip.clone(); newClip.intersect(new Area(s)); } setClip(newClip); } /** * This extra method allows PDF users to clip to a Polygon. * *

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); image = new PDFImage(img, x, y, w, h, obs, "" + mask.getSerialID() + " 0 R"); // The image needs to be registered in several places page.getPDFDocument().setImageName(image); page.getPDFDocument().add(image); usedImages.put(img, image); } page.addToProcset("/ImageC"); page.addImageResource(image.getName() + " " + image.getSerialID() + " 0 R"); initAlpha(255); // JM /*page.addResource("/XObject << " + image.getName() + " " + image.getSerialID() + " 0 R >>");*/ // q w 0 0 h x y cm % the coordinate matrix AffineTransform newTransform = new AffineTransform(w, 0, 0, -h, x, y + h); AffineTransform transformToSet = newTransform; pw.print("q " + matDf.format(transformToSet.getScaleX()) + " " + matDf.format(transformToSet.getShearY()) + " " + matDf.format(transformToSet.getShearX()) + " " + matDf.format(transformToSet.getScaleY()) + " " + matDf.format(transformToSet.getTranslateX()) + " " + matDf.format(transformToSet.getTranslateY()) + " cm \n" + image.getName() + " Do\nQ\n"); return false; } /** * Draw's an image onto the page, with scaling *

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

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

* Draws an oval

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

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

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

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

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

* Not implemented

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

* Draws a filled oval

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

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

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

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

* *

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

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

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

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

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

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

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

* *

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

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

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

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

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

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

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

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

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

* *

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

* *

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

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

* Not implemented

* *

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

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

* Not implemented

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

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

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

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

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

* Draws an image onto the page.

* *

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

* * @param img The java.awt.Image * @param x coordinate on page * @param y coordinate on page * @param w Width on page * @param h height on page * @param obs ImageObserver * @return true if drawn */ @Override public boolean drawImage(Image img, int x, int y, int w, int h, ImageObserver obs) { closeBlock(); PDFImage image; if (usedImages.containsKey(img)) { image = usedImages.get(img); } else { PDFMask mask = new PDFMask(img); page.getPDFDocument().add(mask); 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