From 1592fa6c162db111c5ca0997a6f5a30ca4352e12 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Fri, 26 Jun 2026 23:01:37 +0100 Subject: [PATCH 1/2] docs(examples): rebuild the Book template flagship on the v1.9 book primitives MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Book template example — the document whose construction drove the v1.9 book work — still assembled itself the old way: three DocumentSessions glued with PDFBox PDFMergerUtility, a throwaway layoutSnapshot() pass to learn chapter page numbers, and a hand-computed dot-leader width. It also reached into PDFBox (pulled in transitively), was untracked, and ran in no batch. Rebuilt on the APIs that replaced each workaround, in one DocumentSession: a full-bleed wave cover via pageMargins(page(1, zero())); a clickable addTableOfContents(...) whose DOTS leaders and live page numbers resolve in one pass from chapter anchors; DocumentPageNumbering(showOnFirstPage=false) to leave the cover unnumbered; section bookmark(...) plus viewerPreferences(openOutline()) so the reader opens on the outline. No org.apache.pdfbox, no two-pass probe, no manual leader math. Wired into GenerateAllExamples and the examples README flagship gallery, with a committed preview. --- CHANGELOG.md | 5 + assets/readme/examples/book-template.pdf | Bin 0 -> 35427 bytes examples/README.md | 1 + .../demcha/examples/GenerateAllExamples.java | 2 + .../features/title/BookTemplateExample.java | 234 ++++++++++++++++++ 5 files changed, 242 insertions(+) create mode 100644 assets/readme/examples/book-template.pdf create mode 100644 examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 79282760..f5aaaca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -267,6 +267,11 @@ PDF `GoTo` actions. External links are unchanged. ### Documentation +- New runnable flagship example + `examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java` + — a full novel front (full-bleed wave cover, clickable dotted-leader table of + contents with live page numbers, chapters) assembled in one `DocumentSession` + using the v1.9 book primitives, with no external PDF merge or two-pass probe. - New runnable example `examples/src/main/java/com/demcha/examples/features/navigation/InPdfNavigationExample.java` — a clickable table of contents plus a bidirectional footnote. diff --git a/assets/readme/examples/book-template.pdf b/assets/readme/examples/book-template.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d7351995af01714fcb37e5519fb4ba9327ab990c GIT binary patch literal 35427 zcmce-bC9P?vo6}UZQHhO+qT`){VqcWd-va+hkmBb|InHe}>$R7{hcVL)_n1~!rY+(5KU>McBoy>_CMUCBz?Hny( z7&XjYT&*1)h#3Fw&8TE-Y3@qI@>h2i7)C{RH#=*GzuK(-*8cmayrbD)((10}|LZ4= zfB=lSgW2DsvoQUKY2?fuEZwY#SpNR+>gHl@Y!46P3j&N-2aqfW95Wl zl(4pQGj}0kl&~{)GZ!;A{cGm`IG9&XZjx-Y8Z%NT=v4!OUi+ws66fh6sXBGK=o>~D zfzFWoMsO6&eqhgzt7U@!DqD-51B%F5POXAVuC8^7WbMUPF4glFf|60IHaVOQ zRaHex-7n;&BGokXAw1g#BV5@FlG-Q3<$Y^?6wbZK(({4;4ZF1klU zXZ#}Izyib_Y035$r4+}0VsLoW4K#(tUh#2^$rlz^NwD#V$=L$7-v(*C!~* zzVAil-VL&Y-J+gG5xt#byE=ylk0K@gY4c&(*Z4rRrD%y;Fa8OSF?dS#C;R{HZU4(T z|EK+#nf`(KVGlrnfNFsB{xl}rzmEA2JdyohrC6E&i6>SkD9R29Aq_!(%SI&f%1)*V zz5E=zx&V~5tj~Y5PHrjmvZ1B=Mg>0Pcum&ACZm)9k;M5E`X^@(ya()olwD!e1-Ej2 z|8z4k?hmg)g@BL8COt(+eH{m7+9_t&;#6n5t{=&La~_e$P7taYy&z#rp8mo^tOn>@ zeNzl1WEx}I)7>O=*iX74_Z;Sh!UWrrF$IX;=q$k)T9G(W{-YBOw8ucLzgoW22Ox;W zF6u-WjJK#EaxyW>F2gSKJh+@D)IUl$Ou&?0AO)L``&gaeUmyC8p9Ha04`LNGDtMc! zphW2?$l;x!ceSvb@7i3!9{CU%lx@92V%|fhLp)Hz*A;10X|u$GXYVN}aSS{*WNIqt z-o#a0UU&uC$I!Ua1&X>v$x}cs0~2mq^zLZCgWjC(r74OSe}Dax^*H|34qSf)r~ft} zz#hzdO4CosDZsmUsz@;$vCRHuKBi_hrdd|@1~z7}c?%2+i6fYyF+Bxhy;p}}m@o(! zVrxAiX?|E$S6EVTQX;`n*#Rk4VzTH7_7GSgd_olRKj_E$e-!6f{}AUmS^kN4ZE4CY z;xZ#Or)(G?eHkI57mBld>eZIyDT$zP$c!GD2F;_EsQ8FVeSG-kHKQiY=S4N`A2yEZG99l$DEMU9>`rN8P8qbq&l_heoxCa9M`Mxec|W57W{m@z0pEsybC{;bJ`F2 z-M*{gO-dZypu`a1KdP_>&1*`SVG*~L&oc2%jv4y5Y4>|3eA*hH4-9e%i{2?x{S6>} zn|nnaK5`|3J5h}@wp+brFgl05YIZ3F ze~0D`j>nB@1S^1VPxp#YNgPqy38{u$3y-I6!WPF*Ty|_-1S9pXU_un?8U7j&Z5wjL zA{1@f1ik1b+|!!_Q9yq`2Zi}@6v9BSrz~#j+&YB2J0pu_m^>FauQfDyMKwpmliBMa zgNzf!124WUYK_im<+4-wGi=Fz@{DTPSZsD!tyeunTQY&R0cUXj(Qr_~ucv=n;P*1J zx#7Zo1+=wpCvvqWn*F{zVhKd;Y;}uxJ5i6V%~!y4ubuEZ!0NG3J?_h_6Fmv`LGy)R z%|UNW7I~*Eyw}4N)?qGfsGx4kYM}X&XCqea#38klhG;I&3|6JkPktA(>~14Br{mM(XY6`qbJ3hMgwN4n%p+ zHtt3VV>=q;pog#29ZQ!FV$kKhje*;zUAm6T!b_t*%*;}Y?ib}o0BA%D9>^#>;S zzkz#-KjU8O-*GR4Lis2C{*PFN^$)DV!Or#%!r7MQn8OAW68Ua6gT$xMNK-hpW?~1# zHVU=}9iz`0kZX->i9?74}2!|-&MZ`lOfDAMCzmnxGCu-F#xq>4KTW6d{(NUY3Ayth;u8L-SK zs*|+JQ84%StZ7B{=&oE-bxxsW`z_cUaDxQ<2={pBs*hEx-IA-%XajLWNl%-^8q@?v z>iQ|-a%}HO4*rz;^13-u5&U#$p70}%CIIItCmIB8Kd3w8afH(&Af;YY>;c3&zX^po zc8oQXZd2YG#`cR}`lOF~FECJ~uPwt|8o$m+cj>&wV;@@}-qCqYVL1vIoo{rwUpcJ3wvQ4e{6oD+`em)3P0=MG7 z9|Mc4(Mqf^Y`RKj9}#484yU7X3DzxSOssN*UbfE`LbHVL09e&>+>x_=0rNNwMBWBP zzJV*Ms!MgJ12F|#&Y-RElMTS_=C#fX>vNr(^w+Jc+$Ik2?XOAkm1f26*A_nwoLU&p zpk=D1f~#vJ+K<|JOTPk4cCDwDv&N&nvM%SEcv-- zS$HvXs zWq&EXalltamB^Ud0$#HN1Y-eG;J-b;1Oh&O@85J#695J}3CE?@R>3mllPEGwv9xde zZ>|S*>;@aPB4qfYaQBSWBe9S4CdFYZjXV4OX=MzOSHLh{4u77v2js0rQhH6+3l3%R z9dot!w<7zb5AD0E3V5RYR#p0XpmaCX60@SGL-U|)!AmdvVu#SsZLzZ3%XPg<$8_EZ zuYKoCCy^G-EMGkfIGWB=(>G>FtnJ;bKUChv#)UV!&S=oBiW~U>g*bRn$Ino0G_#!g zGDr)=NoX9^FqZ zJBHkuMu(RNVhV~GFMFExEtFrN!5GW07{wOkb?I_}Jb@yrU}N)9xOlH__;zxR{^DnpU;-%-H88TOf;TXaG@UF?oqCV7ZOESVQSR_#(L zMQ*P8^_vYRPYT3Gs;^jcHdQK2SH=gU&5AZi#l8XG{$JRVvDmvI_^b1}7>jW7hH|C; zNamu2S9VQ-2fj$hS#X8^np)W2&0UP)kwwpptI-JG#ci&6k))9g>U(~t z0$Y~E-B`ES>J3_d+-lCF9CD=vtkxyNI2{mv37Dx%e{g+h=z+rSbEdwL9&&D) zNQ;l~W4RsBN@`7L=nbfy<0y4o7#Zp@^_sDtg0(spwiBueV}&)7OR%4NNxiAMa!ZXsd&W=UW9+-^#Jka zE@#{b5asm3@m+&i41d4zo9vTnyZSE(*{ zv9%R9J$B|x8_t5?QL`S(h*~r!Fk3b}X|z$D1&?2{aD3IHI*F5c&EvP%Hk_fm;I9*1 z;?1__ZM44vPHdGrImk!slLr?9nr%zkS^P9RF;MX7lH*QXx&t;+alcqx;jSD{(#;k$TOxVrT>`!)e|DgPg-a(+$=PtFh7<}VQKlQWAy z;rI`^gDfIdtt$ovgE~`xkLriN^ z|E5rI@e!B}J6$N2co=r4+u5Qs6yku%aC$65T7~m?IIB0=Q?MKZgfM1_;K=$G#tV_t zG&4aD7ySxdRN3@c3uWPw2NDaHSFVcA`wp4&cvrKV_q05mGVP}~{PrsDj^C1f)y7b} zBUT?oUPHw%90&}>Z>O8KoFIx!t&VPN8Fg2tQ1mCydiYM#$iiPm6!4AbXwTEzfvMJd z=!~__>Tp$`tutaW?f%cOX3RH8nHsp0aa!h-DL;k0xQyJ>MUcpjgK~zpv{A{ZuVOC;ii!{~`5YW%(yjP(NuVDwqjr_}V)PuQS}FNO}tZrN03P{}||a z1ImgK*D{TG+}&`@Dc4^Wdd@Jsn}W*}6ZfXE=e#``KikZobOn7@TO*aQKPrJfOpntcYHC`BPz391!seE zpW_a>IcFOZPMbX(-4Xdjc3Rl>L^!85jQVN4_H8|SO&g%jBoQGbG;MF1yFey9j1T!b zLsD7mj&`_FqkQGxETsT@#J3EFMs(C9iGW>LVU(8`CS_?DHG>nn*%uGV_(MykZqiJ2D6yYUXLMPtw&uT-vRTr^r*B_ z%1nv1B(`r`x4;5b_Og|vXn-sG7_7c7sR+5dZv2EC(^*Xe$<66KTg+l=>8W_y*zUp? zo&@Juc+nN(0~NrtY^?c+hOj%jF+bmN`BO>KB1JdxSVfi8w`66!TOEHa&^24-4g8YJ z-ZvusCaQKUNi8CTOL5B<$4))Daxzt{=M$ z42Br784R=-@&PbPgvuxgN(2HOdK7(xvT8xbx@ckPHvrf#{sU-~2-vhwvk7gjZXt>U zp+X|h1R)er0^%UE5ti-RjcF;=H0d@B)5zE)x2dUBao@x9rMurV*9Y61uSYr8v^^9K zC!6ETvmbqH+tbva{=jn5wfcWD%O8qqtSqem$Sm?U1Am35C!Wx>OA@Z6%Zxx~G6fKW zhwu*mK-1XZg0#>JI^Q>%`*}1Pr1%#$M=T4p)TM%k54s{x!4~$ZhV$xMwph)bB))}` zRuOCKSC4KoHMY zcydIJ%A89eRs6?LAFigpQuNt^WI?-DSXVhv#0 z3a##=F_}uU8$m-!x56cZ3G~K-@R5K|IX=RvF3KU&VcS^fiY4&t@Q?t0WZ2TMd13mk z-Vp^r4vTi#f7LfUp82g}d?6bw*b8@+jBvUXb+$i$0)@+$mHx>Ze;^C?e^SCXH39;a zfOrL_fUuw|q;tZc1;tG;gKXpu%t#J`ojfY-S_lK*#kPVp0x|#!r2b>6pYso%!1+&j z>fh#;0Y-(1$Bvu=DbdDW#aiv7Lm)a5TbKc-Xvg*nBx3|61QpC+1R4aui5Rso1w#c> z1!Dzs4LRU%3z>Y!4+JIy22uo;_`{NL{(;{){<&HrfzT0|eRLR1V^I_{5Q7(RcyKvJ z1ej_mxDno1Uf9opY=TIzf#Tq|tE3^sB$GrS=8-HBz(5?1K%ak7hVx(3Yi6c@vZq~& zJB0xlp^3%omTKEvrRmgMe}(x=MaTjkDEXYQEf_#SK~Z)Rhp2IT*v}-{6_VAC`F;eA znafp+KaIoVLZ#PaZ2$R~&|S;c4p-Ad;AuKq)Fn*xmid=zu>=XZG77m0 z+D9>-?wzc`hBTh88>!%K`o&;<-7TqtC+bLcF>dn-#zvudP?Hqq38?%W9HdAg&KiYP z0+8?%ZRtA2*w_llr9&d>O z_(WvW`dmK#ha&+uia?2+bpgHz;CcSuz}_jsGn-52Gea`(@rhsTce;7q9#?d|+5A9r z%mZ3tOf+%Bc zN#NzVKZ)_PWHG+Zs-kRIn@sumoWp+m7!zwT7*W0}Es^T+Z*R131aM4%uHG~?@d=n- z%d9FHYFRmJEO$57C1~}!H@%R1eYt`_O##W(Y@+SE2}Z^V__HPqv9l&>JCq`WCoAtq_0@*%C#YJOD*iE zC~y+0m-HosB%l`0;^1mP+O4AHb?JBvfFqtT@TDI+vVO84$19Sw_gKG0$1%JlU}Kw` z72ao!TI7~NO;126BZ|mLYC#eG1T^q))vA1<1AWOAC~8pt*+=gf9w7~V zbgFkzM%xOVG}{1<6{I z6H$f++YeQm@Di=NO%BJfQbJnqZ^KJddfcXuMC8`SY3E$50opQy<3;du%O+AKr#M-) zF_~tzYx<<8lj&%-a<3?^&Tb5~#n|OoM^_t@XG}%b)}Q7wGQP&YDV}_9gii+$Wg`gv znm8P+D?CzJDP*D~k+{>vrBT$`iM;s;hTDjY;zz|OerF0)FH}-XFj-~f$R-g*B(2a! zdaJAW`in29=VYRg37h0V{){4`nq>5DGfd*{CYk|oW=;o|WA!a~4nMH&9MAFMYupI4o#JQDy7tm3zn{8p@1BdTkg$LGwBmc+eRz%fZi*k($LrAX zi&>=(_*@FL5{Lt*q8p&zpT_i}-jjXjzcl4OfWUSsgp3;-5io?nacMLchvm_9nsFti zg1Yg8QJ9XpFZPv$gw%maz|2?8PR?b;Vk-hP+i#O&#%h7<i9DMZ@|&-hAr1JAjV3?&V>b0NOz3x;Ks+QS zyDn>6`QwaXi%45;Y9lte>nMDi^@2is91qI{X$ZYC3V7Z*HH!7EHpWN=8UOM( zk{F9c>)r+tJ#lithb@tMvXmvbNuW|#_7}QB* zK1CvBpc*9K*zb^-$6`=sAwz!a(D3S$#<7Mu($nPiengSEtk4(2RL@Z7GtLXFl>HS3*=%(@ASUWPZ!-*A`VXTxXf}6I$-x^56n# zlM*Khb&M3v5`rQp%NDL2TIPww?rXv+DTNK2ke0I-&H6ebd}=jUpHrKtMdT$ehITy{*+p?4Dh1DbB$o%FY>wj1noapIB^Ev;Pkcy` zy8d_6y5Z9lEJoPFh&cWB@l~(iosqYRIgCXkQ+uZ}G>`i9hBex*e&IC=L-UE-&*o~|5`l%&g0#vJNsZ3H1qG~tYr5L5YV<$mJ^ZhbdLEaD7UmNR>@x!YX%@ocY!ILusRJyhp% z3i$uuU^v^Y{6vR-_fuAhx-<#pjFDp;#t?`MK~c|EiS>gZwH{(T3M z(cuph=#K9Pwj#evK=%sj3gItTcL?cl_^<>P6BXQEf36hwHtToSU+RKX^zdfi)dfVn z0g}{xnpXF2#iIMN4rZ>fXopN})1PwJ)6j}sR#x329g5EhYZ33b5c>(d@TTiqH^p;u zzi$O#V0tcFfi0m+W@VtJHUYevNuL!N(ybt>~LHia``h_9K;=;+WDIRm*9w9t^n5Du@vVaG}`N1#d+)zt8LVaxMpz@w4Gn7XZTi-rb-2nAZb zrQEXFE6y#aIX{W%zZJU4yU+VEq(?rsQ^X{3S3ky~ERlGkl}wYBF1gv>>~*)y4>Pxj zKtCUR014*yY46dAr|d!l__xSN+k~BRzAJrqd4lR^1=I92&AZIc%N!o;AjMeO ztnB6u_(0H7*4X23^CQK<$CQB-IFS@Qn=12nUe0#cvFFg5;6s~3P98`VjNFg`g6w!`pe26t zChh3URpp(G#(9)98p~e_WMI?Wrx*TpvROkp&x&`BKWrbfqq=|IGswGM#s}#SAqR0u z3ba5UVo7q3Eb!(*G?z+60mgTB=GKC^{|vO8!?anb>_x^10nir~kUhO#&K5U<5EY0Z z$K)*Qd~tT9846d>KBfdwP7r-U|iYtBzC?NqaV|WL~>g^{tE_iFM+`n$EQH0k;3*Lx*D9WytDN(YdQw=+8u9myob{u+i}j+YZ;_OclB0%TG@iL5%ZedEyI58)I% zHv{1HIRmn`nzHh?JN(uOa#Dba)$nwK zKj<1?4o1uTp=nZHBeOXPxBMu!vc}I|YGtstbCOL{M&mcPRsPlQ4B1sG6hZ}fgErJnsjIxXhrTEbEz z`6+T>J`iRO^Yhr>VM~olpO2H@D&PdRnWBj^;rx7#R{=j})?PmCu>sMf;Yj`6HW{}$ z?9$o(BSdiO@m8@9a-hZ})8iWXRjmzzQN^71>=PgwMxb6EL&qx#_WA=VHnuehZs$ds zB5%?x=@#y*(TfK^o;%Z< zUd`7kj8_t_>I3RFwmHAr)96ca_w8mke_AjE;y?b`G9`FThd-g1#K0{Eq zck~a53!Sn?*@O_??@jiv0=r}bxm^$sVQxS=A@el$`R(NTG_XS8z?{R%ZU}0c z!ycbROjLh`o*k13w1H3>p-A-E$i6d$IeIz8K!C1h_xiOE&+SQP45L~D-KUV<5a2wx zw}DB{g}{ry!&ZR*7y!nbiyzlPLH;B#2;bmx_(`A0VR+;ydoUFmx{d;#Cn2xemg08) z`>BrMRfzh#t4`0&b}%wq8ov+XRg-n2{22emD}RRL!qdhg(YJkq9P0%+pLbw?oIjB@~M$ethZLzZzsi5S`CQa`;us2h5i0i#dNLa`@HD z@7OK`II4PX!hchY_Ma#L`hwh&3i$P>62l(~?#%zBi1_xEk97kC1WirR`G;Y-{!laK zU}E|&3S$4ea>32Q!StUi7xp@^PI!`;S6ALwd>$5_mY$CVet%uh(3hsYTu8E8Qy`

p;^)iVObbwWYp=Ups!WOA{lVsyf7tPSu86ge&Yw5N@1+Sm$E^i+IW2?+T5Y7Jo$krFOL3U~4Rd7hZ_DR76k z=!@Q6-Kntc_sezV^Msh{3y=7Ha%*JU8%6pidX!YDZ0n;J)$@b?Yd{d-3sW!&`h+Lw zaZGq5cq<5X=np@``y>?VE8|O0;0t{)&reC<{qqg`&N87E{3Q>Ppkp7a{|n)If|p3p z>DTH$<=F4{bR?RXz#QI}xP8j}mz}3Ch=a?U&m9ujsls3@PMV_dImtQ7x%9dHlVGK= zb~^`A90MU514*724Bvogw3?7bS19=l-f$4ebRVn$w?n*b-!r^dqt|Szz_yKT4sV3L zNIi$sZE?sQ1dzsKqO)-b~f!HhVCOy5e>LZQXPFMC;O3#^kUVQ(Q!<*gbGa3Hl z?*kK0BHtEVw%3}II@I{O8(1LsZ`|q;od|J(Ayxw=`Uksn22v3jcG@m- zbmX`@F*H?Pd>zKTKX)7M-%V(G8bGQCWm!YtyPk$WxMi6v>tVpXna20bEj7o5X)7y1 z%QHL`s%uI$jPbjD5%|A4U}6jm>nCb~$W9_QPv*IO&xm~7Dl!!3qD;Co!o6$g`a3~U z-w+rX`{eH3N?}6v1ez`m1bnB(S{Vi?ih)+q)8zU+hyi9|9Gt@E5=x+h$3o{^W<lIYa83z2P^^zL;mcGWIWc-JSjj zJF1=$$4FFL_B@+exVutReCJ?iH40oh^XD~qvjqnbzk#OIxTVh37m$CVAABz{hg?$S zH>0m&3IYvUz|hb`t_L3M81O+b^h8Hc)-LY^{HRMrOSEX9nf*59+SGxIriN5%$&_38 zhD<={eqA<}OG2hNM@Au<1SQg7vs*+v`hn!dOJgK2NIP!KEDxnw6+ZBjss0eiX3ca>H^$#=Ovo7h6VQU^~zh~uyU+qNdP-+xGP zh@XK#vNqeg7QtRba>&BvFI+Z+~#xTWs>1s{fqE#hTZ(&NwATwtIeuRWe7lvTn z7^$&NpFQ0?wk$PL#a3)0>y>;ea_%BNZp1T)X6)G5shZ(2k-&*tEUdw~Eq>q_(CK_@ z0y>XraN-&DO-N9f=?Gl<$V)`b0O?t%7s*U}IKv5#Z;v@Xlv#IaKZ+rC#c$OI1_X?szez($T)NGdwQh0Q99rB+&NN2}8X!n2pfD-IJ} z6+oDQuqPguMytz{c+7z@cBbe*ucd2S=eED1Qa@@OG@}i|0cP;yuvkn4KpmX{N)(Yd z>!zzJT2L1UOkQ{<1{2lLFIyPlj&7<&Fsad-RO7q~vB*UnDIOFjxqYA=+06*4zA6E0_ z$;d_TIEZF2YfL;3;M8cQ&t0dEj}xkFIZ>YSpxqa0dM^UKFx-F8O)S1NNix48uV`7n zPFgZI;q-*SJl}~*T${vXZn9o(29CMPMjovgP`4cZ>v z>Xt=`eoFTjB{iSLURkXV7fZ#&$F=m_H@44(jl!kb$rVYd_9`qsZ<~GTZMeF-W5F5h zv*A$Pw4k}$BU&hDPSE)Hq7|%f)IyH1PJ!}30!^EhQs;4d`oM{{IOsfYDQmRS=QYY8 zi~V`HFzHxId8Wd=ur#Wr$qzYLzGqE2w>ffK)R_v?t}gB?qkT$6B}}39ddyZkQKvCJ z(_UMWYQRXT#D!R3a>(R?KTIA$60GxVX)+$!fTd9l%ID<6M-&$CTVv{&?+N_)Ev`AH z3^H}|^RP$I7Dv%)-0oG7aJhEq5u;E%q3@;gqSschzJwai>PIe_^@rWpe(2ESSuL5U zMF{y-p`?75wxV1A>n0<$8P+>KL^sYsz!NV!9C+}?Geadzh7->#u1d`1&=F57=3`-C z_r#+}-F6#h(lG?p_c{_S7olJhJIE^Z$Dtn!X9>q4TENTr;fh<4EH?!K%dXp<$jPJA<{#`;S0uCG)EXbyAv{CxhLUDg(t6_RG zfn~B3uYf#m3c>>+g#+XmakRkh(FRMc#YdY_SUo!y9E5_|1{@km6eBnm+$b8kZVJzD z`6?vUs>H9^ zYV@$7H24#+4DkMW7&ce(BJFp4ir+5M{Z`NaOp*5xkZRAEmW>KUYU-L>1%x?Mq#pKO z3_Pls#gCnk{~*1 zz3!LPFw@1BxYXw?<)e^w=EsOgC7(T=b3D0(tpFFejJD&zoNz%jvP+Et0Q(6^z!zg; z9HoZQ=5TLak~-}{Q-9Y|*_vAEbG=@_@1@N|ifqk?Rz;bV>|M#y$))>pG2+^JOo7I1 zCl0zFjxHOkDIqg;=4Gqc=vN1N)N8{|85=*^V3UqAq*1-R&FZ={;4%w0xN-RntBwcc zTL*$Af2e|L)enyDL>Ke;3abPgAw~ou5Wpw%)9PudN53>YGaQIrALrrd_qAlMtU2UE zo=aMIZ01A#!XA<~dD!A?%V)GxfSTwt*WglqsWa zKMhk#<_QGLv`Y^aQwicRIF}@06S&Oj@3hXS99F_$3everdr(D#4@_K8pjdmvEN~R# z4#tg`AWn1~MNH>l${-^*h%AyuAxRh7!qGMdRMj%yESJn^paC*dBiiig{>lLOi4uyi>VGu&ccDh5oaBK#E@cw$pCDPDZB24nc(2l4hk(z<|)u~G?I{Ruk}iqgy^v#YvHi0 zD@?-E*QXS&CY|LU%w{Zo4Y(DX1%`_nXA8imX$;G{IMKbN!OpQXBN^Ge8Tw_iqFZPM z1UVkTQ;nC-e(`g~O8}9qBJcj7yCOyXwbWypr7aU^k#+*6f5RKE@}z`RZh3;Uo1lD- ziMFB-Hb>w-85kkEh`?q342L`h1eIAAF9jVkuVkcU-OU8tKL}JwkmFxR-vwmgDVmA( z^RfynDs)ttbB4^a{~{Je=Dt^-_co!*WQzzgXf|P#C~QAVxu;^`L)pxituoGLZg#Z> zn)flrz}hYxRljrZK*fbEYtx|1IzFj3COjHjT$_%~lgu!oaNrrB&ppkrQ3eeEhIvq%535D(tAtkTU-#ad`p{N&6Jk}&WIOEBJQW6e%wlFH z53@RH-xMy!EJZ`fi`WHmTebt}zw4HqKwv~yUq(hZEb{q&wY1_1gQ_c3vp&&np}(twrrUJUix0S0&~YC1z# zsbIN>TV8SYR@yNg%yyN;GM@5)zK0ZC!xKni)T)*h3Ux)!(INu{LrEew#o|meKne_k zgjwMQ$X7e);_0`W{t83wY$fEnU%}(##HPkvIO7k^o$Hb;q^#1J6KH98xGKuMdid3V zBX{1&%Qm5Ld(|~zR$x2_D>7(ru$e>jCWWUTi%`VNWKs!#2^P!Z0Z77PffzTRZH<>sIl zT-t>Wg!*&Ybas}@16mxT5?;9qpaied=VoHd5Wd< z=azSwflM^zVaAHF<|QvntOpa^%9@8lUt^nk^m8@E81p?e8KmV>Z8^{3b_)n2_o}f< zU*>n(P3@5t*|onQUdXA7xN<0w$v?rF^Jx)(%l9&cIhgtj7hge;u<6|Th65@102#e1 z2O5NU`9BxIwQB-~WJR^)kmRDkxR5VATEe|01c4!hhemc;#Qnc9b9LDZ82jzwlrRYx zqc;=^3CNBqq=xCGBWRoB9&v;CXW_XQ#&8)p;iaPXh0Vk0FE zEnJ|p&oXvgRd?#1svs3tM==e8<^$m4#D;0T&EL-G@5#94&M2y?kn*nVW;Dvs%O@tq zixbqra;6n1MI+s+(a(EgdP*_L^pTt6kB=)Nr;GU!JB0RR!wMIe4>nA(Z(xHw{j8jh zL#cd7#`9QU6@55_AUQ~7iMxE6hu{W~z%5u#`ujLSg5rU0$fD_V(@mtu`MhA8i)DQX zw*$`gP2UtR$_+LY_7Ajp39HcNS;;G(eUJBwHg^0kDn{a(P^Uh;{NEDaD6$(_!>{wt zrdbkHbll^6cJOWZcO z)pFxU^Fs3KsDsj)VXxqfsrNy#`+;2`nxj62)&`X_sSW17EM=}biEi$Ro)`oP#2Z~M ziWGb~?3&ooetw#|Hfz?HT$ki~Bt*+qh|G?sd22YfVe>&aADjF;FtGV)5+AAfWKvAn z%+t=V?$R*CU9m8}&fSu{L-&D%0`>~_kUlc?V^;xS6wi`-s>WB=a0p=+9WM>Gf0f#{hP3uk;@i8eTKRH{eq z+2K)izjLcnjV5@@(a6GKVPAsj+XS1X^F4#Pj#x9SZxocRwSOJA|57aEk1DqG1HUin4&0mP3rzL?~ZfvjJ?F(<} zg*NJg>RT7qvg%ul1>xeBF5s>Ag{@@+dx7Lt{`J02J91trPTmhFI?)8Zi}}n06z2iN zHmmu!^B5@F%S~#{1I7dwnFI#AD*3_y>tyvZJ$?$!49Q{QH%4#>V3QM^0?y?wio=n) ziFeJP2gAy z=acZVpr9sUp6dG>8$+36s|BW>E53$08=e-1z6|&XnjNmlbPtYTc;>T>n!qD`JVQ>4 zr}{MJ0CvqLD=L%I?n@{iwpg#vRO^(jTnnK$7RM{SG9fAZJ$x9f9HFDAx*N`hLk}Z2 zV9)ZRmcX^GhDh$>^RJv3Af*({ zjq$vogohyQ*gL}hk!obK?KNUlkVC8JdN2KI^?cmg3<`-eXyb<>HF^XO#yhP6>#vLU z!;gaB!Jc5MKc`#L4b5LJf`5P7ex$$5IM)&*%wgp}8u@t@eX$e^h8}0%yrVxjo{Z;{ zwMXD^K~elJnM#)mxJSS9dCm*H24fkeoI^GcYE5?RZH<%|#go}5EJ91_IMAr<_2|w9$ej}Khmub^-(6!&m|VY*z=WvX7vy_}VI(2j3Q2gU4ncz@cE=RK zNhtax2mu@;vG~K6eh`W5T|#LmNEQgZLa!Pm*i((@LXlG#y>9fTFp#p{frrd1ik`H^ zehNbB`l0N=FF!hyy=eVdv?pRDAu#q5hR+TE2Aqf^fOu6T1&c{k`JL`W35qFXY>7eW zdj8zTj7zbco*kGCxO#q6q{Fc2XMu$Hg58BNJP=@x*$q@Du#~z7Syw)}qL%cZ|VZ~qAw@5ei_V8HUs*TRf8FBawb zG3WEk+xxq=V9)MxPkX~hk-yKVUC7LzD*1o7T>AgT z0l^<0NwITs{1yey7A%6vi&=h*3zL*eZ7uuXb_?(;%)f4$*+C+4FCQ~LHyA8=84ZMg7%zW*C}+g7O?X1rtx{Hs@j`RL{?3(+G#uU zpqTdzPMn!ME8qeIHqOxp^oe()4~al1$b3!xNOXf4)bmk;c)2Zp(j^HsHVx{3yr)kY zSC@TQ3;I>oYZ-^P}NZJY(L-?Jg=ybQ&K@R#Fz~APMaVy@~>dl#v^N6@&@E!DBoTLYpv=w%Rn< zaEStSSJOa0#!xdxZd%Ymp5l|0R%Z7`#>o^cidJSNNHKr;2$^`17?dM{q8>EpCq(h# z_6Al@*!L(Z<&YLgCw52m$QL2z3921b9ConJM3Dk-R<5E3H}nJdw@GeRqD`?*C5}Z+ zhN?~Pl0*f15kZZH&42}h*1|5n&5@Iz8F3|Ix|TxRD76jFtR)!l*sxPG3UqdRhux9Q z0IYQv5NScPL$A<v-Drq#F?q zm#Ks^_>oEa!Gx(olxmGMCDq6b@eI*LZMX`yXR+vJW6)!rL)-t++B*b!+C*`>W!tvx z`j>52mu=fNyKLKCMwe~dwr$t+EGA-R;=6Gd_eQ*nT)dHs%*8oz;+M}Onltp<2tkH3 zo#BxIErm@C9W>g1svJ=fFV&1wS=lg&^@ZhW2$PnvM+f+QoVFTyq0$qPRa#6+2|R&W zGnq_j;WP2ipJW!H0E~p+D(4)V+vyXPr-1=>OcYWaj-f2{Z=D zbw8;c?GM3}vHldLmkN^q%QBYhqm(F%M~rpygGy zi;AUItjJN*A)3)D4P79Y1D8W92aThg8*^G*^xv2Yihfn((@}QPmCF=XwPi>ku!!A3 zxXvji52n};3MPnGAl8S+6x0dnRZXaSiD(de2X;>DkrOxIPAw&la{ymB^Zq73OYN|1 ztDm1evZP&X5K34Yy|wL%3$po(hy-wXGPG{fl43S z)WX!jUB*I=8Qod1+?3Q0r=l!Wa&=^K6Nq-X(y~u-;Em&bG9rO6n%;wj^InCSvW-1b z-nS%~wzir=#@cOpKFW&RpPyVJy8Qh23Q6dmiyhU$jo%pBk#7Pxtay;f&-w`3vZnTu z_8Jpo$(hm(@Q@@K8^uHUFQ84M%MLO*h_Ny*r@_!sWWlJ`**+AP<5a!o&pLarqH=ld zgi^6b@iK1P51u&*XW7qyNPi3>Xe?-;hR^mA&a05TkpR)PShBk1(FRLiPG1)<4h$;% zTW0bh-ELm(nHJy*__&7lJ=A2S0xc=lP#$qYu^$z1LCvaGn?ja$W4eyZ(L9^GxXtc% zeYFGKBbS-;y~m^u=U_XbTQV=@JDtI&uRVHL)*)4%lbyRaRNSDF-k>SSG>-#hd!yH7 za)tCTM#52gZ0Qm2lLRgwVFOU;c?JG4%5#z)%)B6Dm;eQoTjDV;9{^e7t3w?UArK|a zo03SAO?V+*b~LE`84SP*uDf`M3(#33%m)HuBB2NT}fFl*#``&-4H-2jv~Y~TL5mRV6SY+?&g8r4}o-!vY1L5RHJG*3uY>L2aEQhG>( z;V}mw^^8UOwn&w|>R3qVQ;V?f27EX&W>cup`nvvhbXyVzh?6S~B49CGd2FO)`%@zW zI1A7zm6lGqjd*Gr*6|pmU78EE2Ftu4+xlQBuf^u1M}m#Pbsj2j`{ zkR6OMqb>^?r6LV_5uzR!sToM>j<=RQJO7J~g6`WOLI%83WLy~$IxUAcF*mPc(!w28 zDvS2lN!AADhyG>9pzc~I^GDD+OmZj2oyc-THLvLz{KK(Q?I98!h~Rxb0Ee z1zR!^AUz8VDR?Y+M2bK|Q^|WK>Ly|C*ngCoImPgS2a!K#F?JB)Y>3x1``O(%0?A6C zGsT*NWc`qkY`-BBT^}QU4^9R@O~qYf<@P)js7~;wqoT=OR$Ff+@!p$GT;V#oe6?2(HdaV}JDsL_l1k74vk$-vQvna#+?bhQj_2uMkU0qeG z2=73-c@8;{(b2C0@S+gyk0A|2@2$ZlOCW|7wwm82r*XjSpP7fvzJW==@i$#wEPQvu zg!*Shu>^5~JS-A#?zx5+0uaE+yQ@=Y2aEZtIYD-(QcAC4%5o+GjyfzXCuQ;t{M|dq=Ov_nac~0`Yw)TT`XB(SqIXH%Zy|KPU%K;tY&&! z2u>#?l}m2|F|GMX0A(5OK$i~w0em|>jW63TcKF=Po!w90A_aZt4rp?}WAYuRj9#33 zYec)J>MvS{8~b2-x!ge=N=j8}28MNi`SA=TbxH#^gcQo@G)0n&1Q>dj_^G4Y)K+xgTb=nq(ij7%3GEYw;I(aL88f&BZ# z=da%CN}UbcguL)qxUU4Z=aam)&3Av&x)Y*W#bPRg-yL@bJit5|Z{OoxIDoPh37g4< zDYOd706Yuc5s5fm+}`6Y8#W6`uLyckxW0S>DsHDTp0J0yGPZlC}#L5nuvB3F#_ zF>uwCCKX)Uxb>*{XE~W5P4+v*;YPmhe~UU z7$!Yv3&X$F8AR*VALgF;>xj{}Jgk`3CR z#S+AW&yOUOCsKa)HV|+V4Q_pOy)up2VkNg$qME*5xn3Yx3h^%OuKmKieTF_Ys+}WZ zvK6^U#&d%jU6fl7f!LEBdOoQ;i&}joO9UAwtIDxUD*9z_ArN$pRE8_$5+qlrOS{$7 zQtC*SxFl&LSh_9yLk6#ZEft&!mbgY9ug#u>Aguom$7^4Vk0XL)S_rN~1{NYiCyQwRxF! zD+q%CMh7M}+M(r};tCI`1j{QZXeZvB!t!Dnz8XJqj~a~^IrM0;j%R1R&6QTS3`Z}5 z9ZO&T;3c2r?SPgmpU_E=H^MMf+Y%>O_Wh;IJEk>hJwrVx9A!vnr57s5GbmFi5uD^C zCxB(8MrWhus8$ZR_Rvq>rf*dHa^rnYE{oqwTYtw^!|v)sxwWZNM@t1N#48mMA^Q01 zG1j%rJtq2+yUVT_kEgS{$Ny`u-Yv2oDiVwi%P0~^&yiy* zAUX;Q536#v39gGdpcQU%&uMCj2}<_%okv6@wN*`0xpTg03-!=FJlClRv=>$wOYrtw zy)zZnLgLurw1mhJgxR*0yrQi=>0`xKZhN7IZeLHorTuH$ls?8(2sKJhHEt2uO6z{z;0bbF@QHhn(rcnp z#=G(L@yv}FazgFwG1H4YJ+YS2>Yr{)x$e{j&+C0i9RXlJPN{4A9v&UXHePKDNLUSX z*&I6_-BEQCsNAI5Pl0V>9GHU{Wck$yhdi@eqRh^FoVo5ip0%p6frDD@nb69}1LJs= zQ4(*(WYv22OOHYvhH~J9sShU)mo;Tlj%FLj!wJ?}Y5ugNxP*y?LI~^edG(xZWG9!b zinIiGw()jXPfbiK>7 z>SWw-etXMzTkL@^L^iI4i3R!(YR)n6_eEjmP?vN-Orf5$&`wVvkP`jBLU%5EH?qE5 zIa{wO@6OoIA#T3qs%!_=tu?%Hv~Pr;{|$?YK9Cyp$n=GtG01wrS^iG>j>7Z%0mBf8 z+`~xkxc&8+d|vT@A12g0by{6^Au6YD5_ic$#dD`r)ls_$z$! z0bg2twTc)3ps*j$dn6< z|0VcCg6`(}(nUxGh6|bNcfK2yz>re|l^lJAY?iC{I)=x(`qi*!A{Xa%8mF~I;I`hj z`&P2dT1HSZ)AXF0A7Zk*B%igyZO#q?@Wdp1unQg9=(`p?P z4Hrx1TupkXf5T%S*ct6UD9`AQn&a@Rlvh?P)Y64GvQp17o6{5fcc+2_gU{vzjiyU8 zsVi_{tTI0Jth;JsdncgXneJoMcPTP7tmTQ(<*RyaY0o~C2R`o>Q`$_XR~F)))}4X- zO>$&4@bAS9)P+kpV~>f$7RHDf+~)^&Rhy{ZKH*K@XXS|J^n<=|CT@N}3+-)T`Yec|Z zr#A*M+(xsGgTcAgh@&=e;DIs`PKLaVuse@3BARuPa2HO+J|X-(omdeB&s_Uq3}ib5 zjB6gj%U^~e?^I9k@IMK7F*ceic=Xs?k%jxI{>>&&iuM;}h1#?jefigFQ4JdsaK^`HEXtWFsIBz2VcWOcmyQ@T4MghrMXd_$tdO@EH_NT^@w zW?pNxdAkhdnr4)3;RlCWsP|vUBcw+P_kUy!b*QrQI&HLu zN>%BML+Gcr>3QqeW-hrq0?zscY)W`_Pu&!Xd-Y|D5lUB;K31r2MUyIKN_yQp!TCMT z4bH#OOxl=}dosOH-!-W5d5ihz0~!IbhXWwdKlQh{z0C?z!lD6>pXhr#A@1ON<$Qto zUh&!D_r^rOv_tqne9qw|u`ai$0OURp!~5|4ZSWs_ymsD?VZc0dOZ;;jeJkNc>d+hQ zkK&%w8Z&>4WG^v7ugq1cDO_dxpDQeC;)BnykS4Hfp5eU)owim<&D=!dK5qj6ANd2d zTd%pK68)CQ_aL7|$lVLLo41w}`ubWk?r;o@u{NAT={zeJQm1F~417)_yWPApkWM1k z#gyzG0HskAX;`lXkv7HNyf*4C1(Zn(-n_BLN!l76LIk(1Z0;%;u18>+?7;9f=6R{y z<2J$HCm_=YXbQc*yaphb5rLsOh@9%b3bFbiqbqd@dkj8m{p%g>Ch!!mNHPP{1o+uN zK>ULPRxytw12GwS8lS5TF=|`^3iZ6C;a(QS+s*qC*baIfa4vD7TD>Z;T~0Jvjzq-(oij zt+%LX{^r{oJ;z6%9p2jJS~n4xq7Ywf{jX5`;*Txc5bp=e850kKu(oe0KXA`16^eu& zD>!SgJ=~u#pD!3@dc~vPNg-=jWOTjw>D6I7wYx7e@p&QlS-0MquVV1;!%~#qSdX?O zIgjLtg>k4$Zy_GTZ&CvxqwdIsK0!!Nwa||6DErqWgN;E*H&Vhra>AdpjJMRnLAS7c z5|UuU65B*%5O1z{kK{ya^1>PSyFDelmCUDp^Fr4Y(%!aqa0&Z}k&>re(U>Pf49IGQ zCy}yXRh>X7B-w>j(7QcBL{?|uSUI714_1-V?KNj2dO%_wqh2i1@A;JrkqH~4G%O(J zg|?A`YKd!~ zb-QO7{O1@v1!fzIbosX#zH|_Ye;|K*q)+|V==?vBUd+t@{doU>Lxg)|fBX<4011u3 zJpb3G|3OvYVg~%bF#(v_IN1KLXnm+Rv;*#1#u(Q-V z{);@;TbhGxc8}BEn!?TN9!U02G}5~QLw2`YC|G0)(ltjkHD+#`=Ql+LKg3rYvhs^Q zulxJuy}7{mNeJ6^(8W{~6VXR&PuMrgVz6&UKt;SEi%@LR#35bwRk&vCk`jPl zs)`M6Bs?NZ9*O6x%z+LS5|dc%I%=@b+;Qavy5h21q3YvAE7{NGn_t)WirfFS)G4v5@AjGxmxZ+m-&VQadW1Q0J}s$4ho zhW%=5ZTeEsrnzbf<8CFANDF!LczE%G?&O1zjs)C4&3Y=qwHq1gAWH1wiauRr0(E#6 z)ytmSDztuY0wn$%Uvx;_AgD+76X28h>3nZL=02L2@FSl}Epf#ri`pa_ek$aZnM+CA zUd`*Cr&qpRhFQtIvdjhO$Upr|G(6&pbC+#VgQpxh$Y`|EsFDY;>d4b_xJAK^T>WNf zK8ch!J8@BtX_7QC%60WPyPYfD(B3{aqij&N6G4?>k9~;PVa3zD{+|TyOfG`YG zDUI0$t418M@UoTmgB=n(B-+#_gjrODzDZ*R--L^P<#e2aZ&@VtRJaieQZ6NeJQqn~ zBdyE;xg<8=hhxAt!KG&l3vONE_7>M^WFj*U8!7sn} zj)bj2wKRYOCYWiWUj(vLsuRD=(TDt^q1A>1b7VlSkPc9VQt~GS6|Ib{H-bcy%Pu{M z?vIQTU64rWG^EUhy8{G)$S$~)ipeYJ#vWMY$U}cvRx2Za`rftA&xeSNj0hazr|h(e z2Mvr=kK|S%QEE~{=~$g<x&bOl$oQ25rt9XGXNWFrsP8o8!T5S`SbWf>X)q8qL9 z+S#rpQu%O{Ax&f}!Apd-=4TffoRa$_t&}0^;gQMOnQBNrUHFP0DHyCK zwJY&eATt`);h?jnEk`S^vi_D#w*tSQN<;6WYW@9@x4njdahLUx0v2WUV~Cxq%RB^V z{ZeEYoDG%-J;2IAwlDBoirbN~VlkN-{2Cd(i8+7qPZA?z_r+&(5>EjS`c^|zt3jLT zo;;Sjs#hTgR`xC|Xj6RNuK2#nQwN~!TE<#_TFE6!&3-o5^7Ucrk@8TZxoRD>kP-Ju z(a{6YUZUB&$4|A^{Z2bi!HCS0Vd@QOt;%7C%!KR?+`Mj>g<|t7$X2>eE;2hv1gwZ; zt`fh|99-t#2kF+x0bqfH!2t>d)?Leuqr86;zb{ONJp^s1NY#0Vw;;ph+$xFI_ z5FikG-UkgH#xM!9_;baNpbQAKAQD;6q_|A!<+Mf*E!0g_%w$gI%QW4w3DFJX@EYEp#t=jwBU)VTB^M( z+nb9wlGUWo=NI8kT-+^bge62X!!{?yLjSaDTZ#oH7S4}5Pl(I89C%c>pVp9D_2!)? z@-JQ^YlP+bK<|OO=r96+UI77whCKZ?AQez`fGn=WJ$X79P5)Gp{+@srT{GI*TJ31kj0hLxNLiHGBIv={5OKKRdL@AbR61;EIMc&G} z1lYWeI4WyPXu0#Z!A2FCp1WfoXRVnJytk=K24fFI+8S?R0ymM=7}o)pF+)`dhDSH$ zRNT9fwr+cUy1+ANu<+HNX!2_h!P(ugGkJx0-ruc=x&DL=_h>;N`6KAM8Z6Im`T8R9 zU@GP!&(N=A{9-MP*fqybq7(sVg$c_oFff!L~(m+vZ zhnS{JQVIcw^a$3hIHSeo6|-0?>{#5Tx&aT>YAiDShfsJYyR8V}4*eEkN@w=!lgC-#G%dj5e{`Yznfad#15c{i%?vpFn zci7ySIXTdvatz*0Ok<^yVD;AigSgtm%L0wb>x4FXj9T{gL>{l?Zp)_)p<{p1Hr-Jweqf7`JCsk?AM5a>mp6llD~q> zfpigpb-PuAr?SCjt)*$9K=+7bVETU(SmUl3jfOT8)Z%!e4BmAXQf>^>`@~Wr%NNFc z0QG~Ua~h=RS>pcP%MH8*3GYPAvR{@Gri|nuX7pMcS0&8|C-1j4Mp*F%7JQ+pCaL2< z)JuKV@Scu)6m2afDL`f*(m@1%h_yOV;tEr$$`ctSo+ODbbJUL-`NaHWI)K~0=M6^s z6B-s2pxE111h*T8+{9nOZi;1<$(uyb#XV8+j_IO&m>2~D2OV}^)l8{x(##}UXw#C?=buDHS8`H;;0uy>Y@ zF~!T#(Ipw@{5kHQu~BcGWm!9-o^}=||t1& zUM%CZC+6LCEQo+;_9*9hF=$DiFuiY-eml3Yz43)pCVkQLF=L1b2e3Vmex+#5d*9Io zzXg>7YCyEwEuL1N;|2rnisnPfb|&MV(+mh5$9JN_DbdM1FM!&9xq z#%Fo&uK3TLQKJgp8joHk!K@F1f`9eS19FsU)i$Trne{c!*$z8ij5qIJuc?98Q_!4j z&>mD`$^;vIBI0P%NJR+J(vDOu76%3}2KNedAh!gKI9=RAVi^42=U_JQ0jZL}weE5feA3RD4fz%U`=dYOlkTv;(D!%i=J>*14IT88!{xC@6X{^FV8wX{ z!ZVVtBC)A(%9((}%FT(?883D@Lxlr7Ai?!Iy2!@$8RG^+l`$2QK>ZyoB6kmN{3|pP zmU=`Ye#=<5^Y?9qYXw##VfD<&DGdIhc-XW00MwJ+U>FykfSo^-Su&*X=L1vb?K2Ig zE#DsC7+lR zNxbYl+QxAC@3~>BcRnCrl{tJ{V?yQf-wrbDkOQ{5s*l3jj4GH4=rw4qsMTy_L}l~p z^LX+WszO>SC4a%IUMi~j-jf4hN({ouCgHRR$7Sm}4{uX74wa2K06I%D(zHTpF>tsm ztVojPC`gb+Xs@VWbRO9 zu_T*=698l*%t#eUpjci!IY0<1mp@Q6EfPPd%Z$Ndw!{^_WubNQCR%vCg|U_q21g?n zx1fJoQR8}Qxfv=^5#n)JqW1H#o6b7LZMV*5%ALvp2WOf9v)WE#Q>6afy%lOukIrdm^#1CrD*LHBnEMce0vKjDY%_ zY_1R#Q3Te@q-n-$W8ec6&wp4u7Vnpx%vL+-e@#v0 zZo6Ex@YO8#jXo559E(zty`3US`MqDgUO+y)voKW-7^|8SRkP%GU$SYPi!cb zK}Uv@o*iP9gYP8M)+=GO{&}O9V~;~wJZ*W-oug&LK@#|96?+8*Dd1z3mU@xj2M)ct z834tnOna~WN_2+FwmwE%((tp9RlR3;U^$9#14@`+U%>=l=?UOI9DzfFxZu4sLua^x zdylD7BfUQ=m`N#G;9$bKEQOaS7Wf`QoRJ_;Ma3{9WO(s?=2O}Y4l>QD;5j#yq8Z$o z2fd-luTxWVFj*x{6oHf?($?O!WMd#j46w?xQfqheZyGWIt!Z?Lv0<4Wl8qOKdY6MyJdEI+ytp zx18do^VL;LTbk{!-ZsMKn$ROhpnn=`_ltCN>*cPeOol&Sj=~S)(N?-uCu0%fY&Mk) zBeN?$t|!$fx-G;W;F-e%K80!-m#9ofV9#sjB$)}C)>yRacFy=WCGG!&5Nw8qqG{I= zShDH#k$qW}XFpECu~W9>^B0n0LHg1q*{7q0p7z%nAr~SV0vqcD#e-?^VrvNg0v#R# z4*Q>C>9?^oA}GrCUPek>`_dJ&HT&~(=^{8h~|N5+yWj# zqXuPk3o9eed!>tMd{Tc%O zO-9YU@*?OuH~GtT$HK7z?Y4L;bKx5^Zsubj+|^X7ZjDW@-hnftK>tnFbzPl@=?*9i zT2q(YS5P+NgFh{tDoHUCtuW+pO^vc%=RrJPtdJWzk#S%y>|kaOiRf<*r7k~IZ#d1B zxjQ(@Zls(OZmmlp??&Pso|g11`7_FkUQE{hv-2nP0T(7~zJ{1-?mD0(y~ShWMVyQtdO8_Rs-4l)a!! zU=<>vAIsJ}q+D_6FIVy`N;qR-NS-&-{ufCXK_vE^5Q~cY%lOwqeLO~H_pJQ~Dcf!- zkilriv)UifKbL2(Y}g(rB;J#qOG03u+QaHRX6P7i^%0C+b*qTfhY)R?f3V=U#Wa}s z3K-~JWtMZyp};CWL02ta@3K>+tD*OUt1(Ir)Ze<0^li$*u_A|(E^w0qEj=NY_wQLDZC2V>F5~4Mx>4FB%LV3{ER=z?&Kyw+@QF;!YxI@5@KtCCgA-9JcBgUtNn=y)0I@5N5E=2`M>$smm!mvm;df+ ziYnNZKv^V{$=;I12Z+h=qMg*>_*D%@Kyigo@C?ish3w)E0+9QDn{9E2CAF|(5)G@i z{^;d1?yOvb@+IC)!)=o-w{wC+W+(wOxX_mAHU^a-Ch=K~)PQvymt^fX@(>k3gSB0{ z+cNiDP7H&zrH{kK(q84r^QSJBH@MTOjD?fmd{4C{R8i)9>hV&1G~Q_qBt%*#{`!=1 z{Db#6{BS;=_Z!L1qCupXus%eW;Lgf*9i!q%!p!GyaFW0I@6+1GB5>FC?t9~`N?d10 zZ6S{LQFU3|xRgK?Wo7T=7~;PwX+M+Z-WC^2kJs}bl-k7mzxkvL9B_#Aw0|s9C*^IazV_s+Y_YZcPHBlaLQcLTW zKimbu2$S1Rpx+&!axKFik8wX5`^PZ79#DzjcmBvby*5N)1a83DD|urZ(Dclbzm_o5 zy*OAuJa|_>dS%Wr6Eh<3>69firHL3ZSBme?-W4^ir5*OVe<1WcP66_@eIuJ zPNpWoMLA0;WOeK=E-ofGLrw>E`SsWGN71CvK)1n`PMChTW6k7bs|wM`??q7|f}=PV z#wB_8oh19Kz=5zwCNUK*9L0#SO&BHUabdU+OMsN9TSKAD!PA~59ZJ1@JA=VyK|ANN_bil5Cgi*fshiad}BMj~_ zZ91fYB&FQd%MDhZzrNK(Jp@^%^!x@Ep1a3KV}>)@{_f_6Glcf(HmAk(g> z%X&_KGB_(_Da}zb4dhZTkk=UIXZ|U_#$R^Z*rv=-xtFgJd(t0or$Z>CXE=bk9wCL6 zaoBouV@7XnLb}#O#E2Ryys$l_pCjl6s0wVPbF1>LY1Osa<6CGgHYp_(LnXWvt^-b- zT?m_@a!A_Ex=)#$s|qy_=kQ@@suK)A#Zz>{0xT0$QfMT7lq{E4LTWxMiif52D)-nj0U~zZ<{Z$AX+RSfhLQYg%>-Xh~8| z6!|6uCeIiu*vaK#dX@=XMx!Z@8Ih*h9jms`erW7#!m6{H&cT1S_wp$i9aihr^t2*% zp&u~~j9_7~$nr3a%wL1^^Gm)}nsx8&!6tQtFZgWrUqdFR)j^n^q<42{mO$-iUgH)i zd*CJZd3OvbgfNEp_4S~b(dfa&8O3@a8&7zKWC8Iz(3RE+H^jYZJCj)~NAY62cOOoA zG;!`so^;9O*+d@E^i3swoGFjG4PMv#SXdlEW`&Ec**3o@r;+*2o?Q$d(v9`U zqW93weh@8!CZ_9SX=>IBOc~Y4>U(q@h$(D`BN<-%DAB&87;-+daL1aJDIVXTNkaW-QS3-BMemZ3AK5xw~$Lo zMRyU1sO^C3NbvpG+1X9h87Py8@|)s<8p!FfVliZ zwF~UB=wIo25bFM@h-B0tynEv3*!#J+uAg63om`-^Zckq8SG0rUzMsStkIi#yG}%quk=s zEQ}AK(UblUziJ|iu70=2adEw{-dXEScdZaO`|C~lH8~ECK*oWZ6B{5}W0ci3G#O)V znQD7_pU7wK;Way~nrde_%#Id_(E3kZhQjXx-?m92W_pqZ(kX}X&eNId5g$_`A&F+` zYpgJtnX5j~Lb;{5bT2mlb;bTxz1y6-e)UHG@;zY%Ec9F4)>@H4m+E_=;CXf=Aya6I zrw965eBNA9ABx z?j2!MtZky24`{Ja(6Y3C;^rHu~6wN7gp22W`uOhqX z^5&`e?W7gO3JlQEfyw$_(9aQDXG(k**}J}d9%s3k11_6b30vQzmiXlbx5$5D`0@xF zA#wTFi9>MulM=C8H3WrJeJ=I~z+NoDaQlg$nPG(C`{Y+!kp0Bm8L-3a$O_*zkHl2v zfCJI!)z}+s){A7fAhzH|D5F)ivmzLP3)_ZRBdsN;z9hG8G;~ICG$XjKj~2tYD?un@ z3m5SbGfS{r=NAx4Y4Dxy8T7*Lkm>r9H!~l2te%_Wob#fs?JL(5!p4uj>$XU_7^T&+ z284Tlmnt}~sc(bwXvBAxCdyvnDvIp{;54*?o-r+?u=Mr-w|kA=WZ2h;enK=oH2=20 z@L7^HNM@tX6hW!9uKRJBO(d6XSETnSDS2dRf(JIZc_P@uKYsZe^~%NSVFv|d3I#-u zuA8wei~glY$L3kqxk-Aku2-=X*r*v}NiS_qXg4b0KgN`#RgcuZ% z<+x>bP4@=!;%h`X;13Hz%Degtvvl@_43htww~^>|K_fEL-& zu8~wq)+}eex~RW-DBsTc z6J+tJ;Arj7`(6ZDh5u2#IDSDZfN6LC1KY0V6DL2}h1%D3PulC>b^m@|?$F5E{aiNc zn**2d1N)1#SU%W<=Y_`7fgA580jmXUByR5$-9p49yF=E;A!k-U2_%gq`CDRsBt?qw ziyTivD#$qj(2~_^ArK8)C3j0{_(_E4%d{LadBK54&mvfh%*iRl`*@@F6XuPGkg=+9Ao0z!3Um>DdRPs?u*vP)`&RFt5Kt>YfxYSVZ*i^WoPM%$eiTsG!@L*AJ>yPHw$liKJ7>&&tHC?TH>lxoBX2;53*(Pq`vF%u( zsQ10bcPd%%Yg?dM)$!S$F5D#?1xa7?byIS@^D7pr{M|cyV3V&UxOF|4`3Ko$83~F>bc6OO5}5 zEXmW;|F7)Oe|UKR|9H#$9~M+*4h{eeql%}4DG{TBk<~93TNp-pqW>o&&OfiYld}sE z>p$P&{}F_`($KWukc9iDeONU@w!Q|Xf;n_Tv5Ctcvs-7s-yOD6(@=-8DY5wW6`0m& zu%T67c(gPhrlONS0$;0 zx|;Kmg6lBJ2KPV;z%TbDq{_lTtr(EkmxF5XWP_759Zf#)GX+V=(epBzE4~(TKC29^Nx1_ho%Fq>7rl3tilpli!Q`w>;B`Jm) zSIy!?_i2IPLC&Eh)#{m+ajJ7o#EzuBWt;kAByr(tVjY6kmFW}ui+qC5)+5(*nXlQo z>Mk=aU98B+-_%}MDxowQGH0zY(IA`K51(c&Dr~4UR+pD?dDm~|;2#9vf1U_Ri$;mL;9I2X9vO%H^4Bmq4Rs}3nU*q-l6h<2J zR{9?;>OG|rFVV3JE0+xUzfxC*hFWP~tEgHgJm=)e!psf2oqib33u*eY|4~gX!SU6N z3LcoTlpO^8!NRXW%fg2k1(tr`PzSb(_%aLF-vR(2_eoivIsSY!Jt~j)3I)*Gqrc3D zRCTAyks=n%(NrX2A=8!Sg5?T=u{a88NO$+#rWL27qYHCMe+QuSqjIr;5jezp6`U0m zV_^^4p<;W9AR?|)r&vj%mH7rP&lEM3E%nW9Z``3nx1Ypr1`gWx*}%ulMui=GSpe?s zIv2zd7}uP=`lGW;1|wv73g0iX?`I=u1cPM_k)vj)Ut_`220`l~qv#`2|K;qEmvmzz z0`H;ham9zy_uGM4`%(KI2{I~ye+|Hu@8l;AAUp~+0CC#J?kWei0M~SI0&N|c;R1W1 zt5JR!s=D|776DB-#cR0AXgVRTPoxs+~1PfLP4?< zJS4u{zAQ`hNlPJ!f%*oU*Pl2^<9g{FTc!(knf=_h)Hzk>01T2dVD_8W4X z-i+HQ)L6?SDXCisI`=RY3K-;l(Ln5?%~AhR$8IB z9dRu-%LgSyOA>^PO6&uc7ZSTy@V_>y~GHn0LCeJeMwTL zo?s2TO!&lws`p=2^gmQ$EG+-TX3F;VE=2zmm!S;9C?!gy&C9_o!Xhrp#RL#y0&ob4 zak8^Bv$1gqb8)eY3JbA{i?fP~i8Bj{a54dy*jYH3*#Y7rq8vh^LPBh8%))G4n6MgyD5>>9s#S*gxOUm&Qwf{x@f$scMd5NbhPXVdu8xF$-lrX}u%3jF z*Kor8g9#m~Z6D+>-X4iF({~j50teazi;iXfV?A`2|8eKeE{0An9!~#Gkd=+=pMZ^= KTueb6=6?X`ZIl22 literal 0 HcmV?d00001 diff --git a/examples/README.md b/examples/README.md index 868097d1..0a51f6c6 100644 --- a/examples/README.md +++ b/examples/README.md @@ -117,6 +117,7 @@ are with the canonical DSL, then jump to its detailed section below. | Financial report one-pager | Single-page monthly financial dashboard — three margin gauges, cash & stacked-OPEX charts, a revenue donut, and forecast bars; all v1.8 native vector charts plus inline sparklines and a path-clipped photo masthead | [PDF](../assets/readme/examples/financial-report.pdf) · [Source](src/main/java/com/demcha/examples/flagships/FinancialReportExample.java) | | [Master showcase](#master-showcase) | Kitchen-sink "Q2 sample report" combining the canonical surface end-to-end | [PDF](../assets/readme/examples/master-showcase.pdf) · [Source](src/main/java/com/demcha/examples/flagships/MasterShowcaseExample.java) | | Feature catalog | Browsable reference PDF: every shipped capability as a block — outline-clickable heading, the exact API call, the rendered result right under it | [PDF](../assets/readme/examples/feature-catalog.pdf) · [Source](src/main/java/com/demcha/examples/flagships/FeatureCatalogExample.java) | +| Book template | A full novel front: full-bleed wave cover, a clickable dotted-leader table of contents with live page numbers, and chapters — the v1.9 book primitives (`pageMargins`, `addTableOfContents`, `DocumentPageNumbering`, container `bookmark`, `viewerPreferences`) in **one session**, no external PDF merge | [PDF](../assets/readme/examples/book-template.pdf) · [Source](src/main/java/com/demcha/examples/features/title/BookTemplateExample.java) | ### 🗄️ Legacy diff --git a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java index 1ce12cf2..2a823953 100644 --- a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java +++ b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java @@ -45,6 +45,7 @@ import com.demcha.examples.flagships.EngineDeckExample; import com.demcha.examples.flagships.FeatureCatalogExample; import com.demcha.examples.flagships.FinancialReportExample; +import com.demcha.examples.features.title.BookTemplateExample; import com.demcha.examples.flagships.MasterShowcaseExample; import com.demcha.examples.flagships.ModuleFirstFileExample; import com.demcha.examples.templates.coverletter.v2.CvBlueBannerLetterV2Example; @@ -211,5 +212,6 @@ public static void main(String[] args) throws Exception { System.out.println("Generated: " + BusinessReportExample.generate()); System.out.println("Generated: " + EngineDeckExample.generate()); System.out.println("Generated: " + FinancialReportExample.generate()); + System.out.println("Generated: " + BookTemplateExample.generate()); } } diff --git a/examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java b/examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java new file mode 100644 index 00000000..38a961a0 --- /dev/null +++ b/examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java @@ -0,0 +1,234 @@ +package com.demcha.examples.features.title; + +import com.demcha.compose.GraphCompose; +import com.demcha.compose.document.api.DocumentPageSize; +import com.demcha.compose.document.api.DocumentSession; +import com.demcha.compose.document.api.PageMarginRule; +import com.demcha.compose.document.dsl.PageFlowBuilder; +import com.demcha.compose.document.dsl.ParagraphBuilder; +import com.demcha.compose.document.dsl.PathBuilder; +import com.demcha.compose.document.dsl.SectionBuilder; +import com.demcha.compose.document.node.DocumentBookmarkOptions; +import com.demcha.compose.document.node.DocumentNode; +import com.demcha.compose.document.node.LayerAlign; +import com.demcha.compose.document.node.TextAlign; +import com.demcha.compose.document.output.DocumentHeaderFooter; +import com.demcha.compose.document.output.DocumentPageNumbering; +import com.demcha.compose.document.output.DocumentViewerPreferences; +import com.demcha.compose.document.style.DocumentColor; +import com.demcha.compose.document.style.DocumentInsets; +import com.demcha.compose.document.style.DocumentLeader; +import com.demcha.compose.document.style.DocumentTextDecoration; +import com.demcha.compose.document.style.DocumentTextStyle; +import com.demcha.compose.font.FontName; +import com.demcha.examples.support.ExampleOutputPaths; + +import java.nio.file.Path; +import java.util.List; + +/** + * Book template — a full-bleed cover, a clickable table of contents with dotted + * leaders, then chapters, all in one {@code DocumentSession}. The reader + * opens with the bookmark panel showing Cover / Contents / every chapter. + * + *

This is the flagship that the v1.9 book primitives were built for. Each part + * uses the API that replaced a former workaround:

+ *
    + *
  • Full-bleed cover + book body in one PDF — {@code pageMargins(page(1, zero()))} + * gives page 1 a zero margin (the cover bleeds to the edge) while the rest keep + * book margins. No second session, no external PDF merge.
  • + *
  • Live page numbers — {@code addTableOfContents(...)} resolves each chapter's + * page in one pass via its {@code anchor(...)}. No throwaway measuring render.
  • + *
  • Dotted leaders — the {@code DOTS} leader stretches a {@code line().fill()} + * between label and number and rounds its caps. No hand-computed leader width.
  • + *
  • Unnumbered cover — {@code DocumentPageNumbering.showOnFirstPage(false)}.
  • + *
  • Outline on containers — each chapter {@code section} and the cover carry a + * {@code bookmark(...)}; {@code viewerPreferences(openOutline())} opens the panel.
  • + *
+ * + * @author Artem Demchyshyn + */ +public final class BookTemplateExample { + + private BookTemplateExample() { + } + + private static final FontName SERIF = FontName.PT_SERIF; + private static final DocumentColor INK = DocumentColor.rgb(20, 35, 80); + private static final DocumentColor BODY_INK = DocumentColor.rgb(35, 40, 55); + private static final DocumentColor MUTED = DocumentColor.rgb(120, 135, 175); + private static final DocumentColor LINK = DocumentColor.rgb(20, 90, 170); + + private static final float M_TOP = 96; + private static final float M_SIDE = 78; + private static final float M_BOTTOM = 84; + + private static final DocumentPageSize PAGE = DocumentPageSize.A4; + + private static final List CHAPTERS = List.of( + new Chapter("CHAPTER ONE", "The Sun Had Not Yet Risen", LoremHolder.LONG), + new Chapter("CHAPTER TWO", "The Sun Rose Higher", LoremHolder.SHORT)); + + public static Path generate() throws Exception { + Path outputFile = ExampleOutputPaths.prepare("features/title", "book-template.pdf"); + + try (DocumentSession document = GraphCompose.document(outputFile) + .pageSize(PAGE) + .margin(M_TOP, M_SIDE, M_BOTTOM, M_SIDE) + .create()) { + + // Page 1 is a full-bleed cover (zero margin); pages 2+ keep the book margins. + document.pageMargins(List.of(PageMarginRule.page(1, DocumentInsets.zero()))); + + // Open with the bookmark panel; number the body but not the cover. + document.chrome().viewerPreferences(DocumentViewerPreferences.openOutline()); + document.chrome().footer(DocumentHeaderFooter.builder() + .centerText("{page}").fontSize(10f).textColor(MUTED) + .numbering(DocumentPageNumbering.builder().showOnFirstPage(false).build()) + .build()); + + PageFlowBuilder flow = document.pageFlow().name("Book").spacing(0); + cover(flow, "Virginia Woolf", "The Waves", "A Novel", "LONDON", "1931"); + flow.addPageBreak(pb -> pb.name("afterCover")); + contents(flow); + flow.addPageBreak(pb -> pb.name("afterContents")); + for (int i = 0; i < CHAPTERS.size(); i++) { + chapter(flow, CHAPTERS.get(i), i, i > 0); + } + flow.build(); + + document.buildPdf(); + } + return outputFile; + } + + private record Chapter(String kicker, String title, List body) { + } + + /** Clickable contents with dotted leaders; numbers resolve in one pass via chapter anchors. */ + private static void contents(PageFlowBuilder flow) { + DocumentTextStyle head = style(26, DocumentTextDecoration.BOLD, INK); + DocumentTextStyle link = style(13, DocumentTextDecoration.DEFAULT, LINK); + DocumentTextStyle page = style(13, DocumentTextDecoration.BOLD, INK); + + flow.addTableOfContents(toc -> { + toc.title("Contents").titleStyle(head) + .leader(DocumentLeader.DOTS).leaderColor(MUTED) + .entryStyle(link).pageNumberStyle(page); + for (int i = 0; i < CHAPTERS.size(); i++) { + Chapter ch = CHAPTERS.get(i); + toc.entry(titleCase(ch.kicker()) + " · " + ch.title(), "ch" + i); + } + }); + } + + private static void chapter(PageFlowBuilder flow, Chapter ch, int index, boolean newPage) { + DocumentTextStyle kickerStyle = style(12, DocumentTextDecoration.BOLD, MUTED); + DocumentTextStyle headStyle = style(28, DocumentTextDecoration.BOLD_ITALIC, INK); + DocumentTextStyle bodyStyle = style(13, DocumentTextDecoration.DEFAULT, BODY_INK); + + String mark = titleCase(ch.kicker()) + " · " + ch.title(); + if (newPage) { + flow.addPageBreak(pb -> pb.name("break" + index)); + } + // The section is both the link target (anchor) and an outline entry (bookmark). + flow.addSection("ChapterSec" + index, s -> { + s.anchor("ch" + index) + .bookmark(new DocumentBookmarkOptions(mark, 0)) + .spacing(0); + s.addParagraph(p -> p.text(ch.kicker()).textStyle(kickerStyle) + .align(TextAlign.CENTER).margin(DocumentInsets.bottom(10))); + s.addParagraph(p -> p.text(ch.title()).textStyle(headStyle) + .align(TextAlign.CENTER).margin(DocumentInsets.bottom(28))); + for (String para : ch.body()) { + s.addParagraph(p -> p.text(para).textStyle(bodyStyle) + .lineSpacing(5).margin(DocumentInsets.bottom(12))); + } + }); + } + + /** Layered title page; on page 1 (zero margin) the waves bleed to every edge. */ + private static void cover(PageFlowBuilder flow, String author, String title, String subtitle, + String city, String year) { + double pw = PAGE.width(); + double ph = PAGE.height(); + + DocumentTextStyle authorStyle = style(15, DocumentTextDecoration.ITALIC, INK); + DocumentTextStyle titleStyle = style(54, DocumentTextDecoration.BOLD_ITALIC, INK); + DocumentTextStyle subtitleStyle = style(22, DocumentTextDecoration.BOLD_ITALIC, INK); + DocumentTextStyle footerStyle = style(12, DocumentTextDecoration.BOLD, DocumentColor.WHITE); + + DocumentColor pale = DocumentColor.rgb(206, 219, 246); + DocumentColor deep = DocumentColor.rgb(120, 152, 218); + + DocumentNode topBack = new PathBuilder().name("TopBack").size(pw, ph) + .moveTo(0.0, 1.0).lineTo(1.0, 1.0).lineTo(1.0, 0.82) + .curveTo(0.66, 0.72, 0.33, 0.95, 0.0, 0.84).closePath().fillColor(pale).build(); + DocumentNode topFront = new PathBuilder().name("TopFront").size(pw, ph) + .moveTo(0.0, 1.0).lineTo(1.0, 1.0).lineTo(1.0, 0.88) + .curveTo(0.70, 0.97, 0.30, 0.80, 0.0, 0.90).closePath().fillColor(deep).build(); + DocumentNode botBack = new PathBuilder().name("BotBack").size(pw, ph) + .moveTo(0.0, 0.0).lineTo(1.0, 0.0).lineTo(1.0, 0.155) + .curveTo(0.66, 0.205, 0.33, 0.075, 0.0, 0.16).closePath().fillColor(pale).build(); + DocumentNode botFront = new PathBuilder().name("BotFront").size(pw, ph) + .moveTo(0.0, 0.0).lineTo(1.0, 0.0).lineTo(1.0, 0.11) + .curveTo(0.70, 0.05, 0.30, 0.17, 0.0, 0.095).closePath().fillColor(deep).build(); + + DocumentNode titleBlock = new SectionBuilder().spacing(10) + .addParagraph(p -> p.text(title).textStyle(titleStyle) + .align(TextAlign.CENTER).margin(DocumentInsets.zero())) + .addParagraph(p -> p.text(subtitle).textStyle(subtitleStyle) + .align(TextAlign.CENTER).margin(DocumentInsets.zero())) + .build(); + DocumentNode footerBlock = new SectionBuilder().spacing(2) + .addParagraph(p -> p.text(city).textStyle(footerStyle) + .align(TextAlign.CENTER).margin(DocumentInsets.zero())) + .addParagraph(p -> p.text(year).textStyle(footerStyle) + .align(TextAlign.CENTER).margin(DocumentInsets.zero())) + .build(); + DocumentNode authorBlock = new ParagraphBuilder() + .text(author).textStyle(authorStyle) + .align(TextAlign.CENTER).margin(DocumentInsets.zero()) + .bookmark(new DocumentBookmarkOptions("Cover", 0)) // the cover's outline entry + .build(); + + flow.addContainer(c -> c + .name("Cover") + .rectangle(pw, ph) + .back(topBack).back(topFront) + .back(botBack).back(botFront) + .position(authorBlock, 0, 196, LayerAlign.TOP_CENTER) + .center(titleBlock) + .position(footerBlock, 0, -54, LayerAlign.BOTTOM_CENTER)); + } + + private static DocumentTextStyle style(double size, DocumentTextDecoration deco, DocumentColor color) { + return DocumentTextStyle.builder().fontName(SERIF).size(size).decoration(deco).color(color).build(); + } + + private static String titleCase(String upper) { + StringBuilder sb = new StringBuilder(upper.length()); + boolean start = true; + for (char c : upper.toLowerCase().toCharArray()) { + sb.append(start ? Character.toUpperCase(c) : c); + start = c == ' '; + } + return sb.toString(); + } + + private static final class LoremHolder { + private static final String P = + "The sun had not yet risen. The sea was indistinguishable from the sky, " + + "except that the sea was slightly creased as if a cloth had wrinkles in it. " + + "Gradually as the sky whitened a dark line lay on the horizon dividing the " + + "sea from the sky and the grey cloth became barred with thick strokes moving, " + + "one after another, beneath the surface, following each other, pursuing each " + + "other, perpetually."; + private static final List LONG = List.of(P, P, P, P, P, P, P, P, P, P, P, P); + private static final List SHORT = List.of(P, P, P); + } + + public static void main(String[] args) throws Exception { + System.out.println("Wrote " + generate()); + } +} From 8fdc86b46967e0f75aa1fb78443b9c0545ca9f50 Mon Sep 17 00:00:00 2001 From: DemchaAV Date: Fri, 26 Jun 2026 23:51:53 +0100 Subject: [PATCH 2/2] docs(examples): tidy Book template Javadoc/import; refresh the preview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop the stale 'Contents' bookmark from the outline description (the table of contents emits no outline entry — the panel lists the cover and each chapter), and move the example's import into the features.* group. Refresh the committed book-template preview so its contents leaders sit on the baseline, matching the TOC leader fix. --- assets/readme/examples/book-template.pdf | Bin 35427 -> 35426 bytes .../demcha/examples/GenerateAllExamples.java | 2 +- .../features/title/BookTemplateExample.java | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/readme/examples/book-template.pdf b/assets/readme/examples/book-template.pdf index d7351995af01714fcb37e5519fb4ba9327ab990c..492f3e3714f9458da057c51ca2e35c63c9cea77e 100644 GIT binary patch delta 689 zcmaDnh3U~0rVZZAjAoM$GAU2aXO^xH+PlRwIM8ORsEF;9rbpY$h1M<;5Lv=~bNd;V zd)51Szi~hD$XV3&#xii@p?gaerB_rOxL5G^*N&L;ZnaT)f!b#civ(wd){64$&vsvmQN51zu>k>FBbB)^h zBI-9vb?-ZUeADWCjCm=y8rS`?6fzacR;<#y?PVL|9NC(~mXjj!WRha?+sPH-f<99f z*-lU4zi>hNo@ec;7xPTkrWiP;&;4|4Nl18!+G7X(7h9+9vf6R%i{xinf%U=3cONAE zdvK}g-wM{UD_y4D&le^b-{Cq{b|cO#)%GRVJqn;V)MS(;lKnwdGf zI2&2G8d|s*Il7q`o0=Lrxmg&QSs0rdI~kgrxLTOIx)@rRI-A)k*id2w3&@PgY@M<7 zclAmZo}MO`adVpS(-=Oc#={Of7@Af&D^5`1VA<+n!?}-hmC#XF0hw8=HQwvbOL_Kt z(nOPuMXtLzdpHceJPcYhr%X&@+q^>IuuM^iVZQANsgoBq-dvUOeexqjueL4Do!M8N z*|Ku#>Bj}}1y#Ds^^2aGeVMj7;@r6>f7HKhTOH9~^woLY{`P$xOtK{#TT<5~h-OC| zXEVE4{5U4B@9DLvcmDB8N8dQcSGKXeR{Zx>;?Um0wI3o z7ChqsQ`^<@I>)2NaiSIn&*~|ugq@5$w@*n&@_Tg4XS38rj&k?hdlvFniW)VGMbvjn zb=93Xy-E8y!}dqp7UWDkFErj3PK;Qcs_cg08MK338{N}moG}p2>0?nzX zsxw~5zJFSC>P4f;+9MJRH~Up42MKjW`T5Aa5Z$}Y==FoFcihja72=lP{lzZ+nSDCj z7j@nZi^JAL8uhcze&`oqcKF+-nc;rXANp3kReDp=klUkGtkd9J)a9$jevMK7PTGO{ zk$;#!ynHy#-zjmyugMEJgg5VDIo4cnX6S6>=3;4J;b>rC=ICl}YHDa=V&-INY3kzS zXzb=@?BeQX=;&;2U}0ctWM*h;;pXgO=IG++XkudMWa8pzr(i>g5iB4xCbM?N*1y#& zS$JD-%hJ^7a{+x%6$~s`6k`}P3N%>w8XZKsk4#>m9WYfxuj#>sJ1&*=A61O+Pw!c> zF*JC|>H{4CPCYEbGh8G!ml^bO8HEHo_es8XycW>cmY9F-VuYUT>5u6zN=+`56e+8( zFRB!ISL~<%^o#4dJ8k!h#7^JrjQP#E_ie>st_ipICoXRH%}JEDzEN5zUswHN_Ydw$dG>caPIgBo?hf7gTlIT&#iaA+ kKB~prKaQ5WQnhHz{057H`Hvj+t diff --git a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java index 2a823953..2a19aa45 100644 --- a/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java +++ b/examples/src/main/java/com/demcha/examples/GenerateAllExamples.java @@ -40,12 +40,12 @@ import com.demcha.examples.features.text.RichTextShowcaseExample; import com.demcha.examples.features.text.SectionPresetsExample; import com.demcha.examples.features.themes.CustomBusinessThemeExample; +import com.demcha.examples.features.title.BookTemplateExample; import com.demcha.examples.features.transforms.TransformsExample; import com.demcha.examples.flagships.BusinessReportExample; import com.demcha.examples.flagships.EngineDeckExample; import com.demcha.examples.flagships.FeatureCatalogExample; import com.demcha.examples.flagships.FinancialReportExample; -import com.demcha.examples.features.title.BookTemplateExample; import com.demcha.examples.flagships.MasterShowcaseExample; import com.demcha.examples.flagships.ModuleFirstFileExample; import com.demcha.examples.templates.coverletter.v2.CvBlueBannerLetterV2Example; diff --git a/examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java b/examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java index 38a961a0..35b8e043 100644 --- a/examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java +++ b/examples/src/main/java/com/demcha/examples/features/title/BookTemplateExample.java @@ -29,7 +29,7 @@ /** * Book template — a full-bleed cover, a clickable table of contents with dotted * leaders, then chapters, all in one {@code DocumentSession}. The reader - * opens with the bookmark panel showing Cover / Contents / every chapter. + * opens with the bookmark panel showing the cover and every chapter. * *

This is the flagship that the v1.9 book primitives were built for. Each part * uses the API that replaced a former workaround: