From 13448f6b1ab6030e4826bd17092d26bb6a6d179e Mon Sep 17 00:00:00 2001 From: danfickle Date: Wed, 24 Jul 2019 18:14:29 +1000 Subject: [PATCH 1/6] Tests for column layout (1 passing, 2 failing). + Test with simple text only (passing). + Test with nested elements (failing). + Test with nested floats (failing). --- .../text/columns-simple-unbalanced.pdf | Bin 0 -> 27095 bytes .../html/text/columns-floats-unbalanced.html | 49 +++++++++++++++ .../html/text/columns-nested-unbalanced.html | 56 ++++++++++++++++++ .../html/text/columns-simple-unbalanced.html | 38 ++++++++++++ .../TextVisualRegressionTest.java | 27 +++++++++ 5 files changed, 170 insertions(+) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-simple-unbalanced.pdf create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-nested-unbalanced.html create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-simple-unbalanced.html diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-simple-unbalanced.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-simple-unbalanced.pdf new file mode 100644 index 0000000000000000000000000000000000000000..109d457159259715df946498a0ccbbba2e6d5eb9 GIT binary patch literal 27095 zcmdU2cYIaFwhn|6jzAzZL6JiVJtwCZKp+rGfRF&81};Q$2!W8q6d-`~BK4vaMT+zy zDvFegG-*Hj*7Bj?^T?nn#rO9m5*&X=N#*EO!HuMW^6k>vb zW~1JyZRppXp`HwNWGI>;|3)EJyI$w87|j-l>B5LT80y7PABGAQ+JUihOQQ;*aW7Ps za%;HVy&DbC#&puOizzgoCX3FB-gQSayith7uCv$-W)s2ycO-=PNvJnNfegV$lf_`x z8SNIU8FJb)Dm<6ubYlqaNHngIOX2!CC#Ru{B$+*Z3X0LBLn0_Kv13r<#{gf@ixNV> zC4@wg5U0U2i3VRJ#5oZWiPniBMAmGxBH#6TjHrfw@LS?V7DTvYKtoU^1m`4#Fh~d{N(jzL2*H#P43-d=$q|={pctu;7|cdH+18iI zLAps&5h4k3FAyS$#=XG6kZ3-QETu$6pwrAco6TzTbeV);frQZ65<)yA1PdetTP1`D zV8j}Ac8pv*B7lJ_QF%*5QlfFhIn`1TSE(hpj@!fKbo3?J>{dj`n`;;z2BS{zDMRQ% z2_b_dgosH9(UcIfL_!rAqJd<02aF27gAsW{gh&ztMPo{6(fe5pdoq!Fh`JMg2IHO8wG{6>E%l%Ta7xCQZ?Px$8L65>4J)w zpo(}AERYaPkPv*bS*$utebHMD{oG~C+YmNMtXvFILZWd_gjb@eRJd!ZR|OZNV&zh~ zZM-3On7hdB=IVV4G$z~z&o!Egwa%x%Zen_8ybf-ec0{JtiBy$MSh{TCc<9 z)<9*Oto3rql8`LQdGgQ8Ls9V_%jc1h%6YD#zC@8V$o9#tWm{j7+H$E@i6Xb?;7cl- zFxOU!GR7Q5+=kKaj9FUG^B$>gGmzN8e$j? zDJ5}onQ+vsw}}}Pm&18cy(KxmO(Zd!Rw|+iivw&0Sgj74t3kGe;CF$DbE1PKP9%eb zI469OXo!h~5EBXUc8Cc!9mFDr%R$;oEins`5c)(y+%}FlXOTtEfGdKiunhuTqC!J z>&I+B($6`OL=p|vMnXjlf+I>Ld-*pB9!F_;W8O(afQJTXwH%U43j|5iqHdiF$ zI`0;U@VgJFn7GXN2NeLRD%R!*Iqf@I46ZE-*e4-4D>ta!R8NKDJy( zC(<6V*_D0pL;$3?06wz8K-W&JiFPuOEkXbjjqnKy1B?y5CfFDyP^K`Yh>2)}91wu9 zp{E4}JuLt;2k9j!jIGGMNG06zsvROsR-MtLH&9wr5fKOuN(cr@2(}wcde?-;M$-hE zw1}2m4pk&U!_`MZh>nD)mI_YZj%uHvsr5HyRD@aLMXE@Md(7?TG~Xl2R_W*>gG1T& zrVE=uG}RD@F2n*M6cU095<(~>gz!oTp|GF`JM?DPmLP9fn0jsuSD_Nh#c=&xAs2&T zQLv@leZ*d3jVfwIsbn)PzdQ?fq^Q9{HwtXDCL&7&qP;zVDAocYK?+2ilWRbbu+q_C zO|SRtt4LzVAE5$cNkXcI7~v9;+f6fWA%XdFoL&SwB}C2}+{;6X8Vr?)uuMYe7zwEw7N!Himm2;9F-UExAubpaLVzWtvIe6- zBBHw`)XnE)mRIA+gcBW2|$ocp4v@gfr^x($?di1*j+kmD;8L>CK*Ofl2NtSIu9jnW5flL2NJ zu@h@iZpA7987(LbK(C;{bk9nJHsP2!L{{)oHC1HuMo2nAV!|<-5)=eN04AGh<+52L zx)dokFxgBTT?Q03WS9U<8f)YG5Rms8AP9z5O>cZI5AuFT1BlaE z%@&7uPOSj4M*$Q(1;9TnGI8&gE^n?)>;H`aB%=^bt}1%SRRt7Gq0CU&sEZWfqYJDJ z<%}YLdS3ywkD-8qMF$qOxbaqLwn+l_jPw{2YKf{izb-649%Lvu$wKj1dpLvefW0q)p)0JDMWtzsQb z#}wdB5KkM%rn1hR9v(*4D%#h~WE&Y6pEjM@ZZ^|00WlH)X(9l;6#!}lfPF?>QY=Q- zo-@o7B*upj2?}q7awtel6GAB{%sO;T0TgBeYr`nq1SSqcLda$&u$VA5CfnyUmN!83 zv<7mR3=YqIXaS%|03?S1a8Cf3AOPGm=y7Mv^Bf?fpdPm}p2ffp5UMt-4%-n;N}cwVb;7_An1vE={7TNO0p!4%tOveHdB zikv{yZvr8XW`{{2F7JCsGVYp*;vDj`Y@L30;jFDG@e zpt-LrUK%BXpm9!EZ^8>dc*?>^#f!62PKk!@E1*{>SFaL7bw)I#!X^+G!>!>$X}F1Y zDiz#PF4U(CS9X2sqccrdLFi4iW~K}ih!R5})P@2PrGeGq+Cp>PVc{*&AyP|9D2%R7y zSRo;}CLshtLI{F{;0!v|b*I8kS4`ZJm8jerR6>cy^>a%Rz5+Q5v&N^ADeb`+Q0`DO zv5f%Jscb8Nu2T5}*d{1c+!Z#&0{2Y3Z;!wuL4wf&FbZ#k;0iw0h%QEoCT0UO6;Tyz zOd40jBrwmh>m?*G3X@&rh9adVI#Ik#;vR%}UEKA)B7%T1;YBdcf>#V^)O%JR5)u0> z1tKogT`jyqIn6y>y)<{V@FMr%^&*uqzBhCaWv>{n-d#Jqw2I6nmBI#$$SqY1^`Sn< zn>EN++A?(5?4D<@B!t@+5`rQL@pdptqQNr>!54$UifvOoZ$-5piB<>|DK1gb4H80U zN{HK|*21aXe3=WlCS~6DbfC+DFABJC9>6F8kf#E`RRJ&@1i%$k0K`WC1V8}fk^o2o z0pJm`-}TNpRc^gZV#EmQ5KfB@1=mEg zE<{iOs1|_w7i%}#2Bgx=7?G#GF~a@=8L>hUG<3KC2#x^oS^y>wV_i_tK>{!~^s%6* zyfVl%rit0W0Mk_D*17WdrGafgU^H8FW~=*Y9+&f8;L?>ZKyryTNG<`$D-2Y6zS6t& zP=vd@^2&4duot+ZtuV!~CeSXhc&n1~UUAH z>Fq|kUj+Ll1fwJb^Cg5RNC=*qFuA19f6#o96S+{XFz|mW20dM{e)oP?{!Y13J4mFDw~5IL59K7dEN%uFVPSa2~p7% z@_0K=Ll7iR^tyzE9dwB;eG@{?s5%O>9 zCc<7mSZuV*C%g?QW$3)ZK>1B4yb=%<7e?`Q{ifuQ1t|@^5@~Hl3|{Pexnr9I(686o zO?KC#cQ8T#^sfL2hyc_t4%b^7o(d^QOdL{LP~fltj19pP6fT>Qm<@1WG(xZhV2T)E zY)mxQ%J^6qI3GeU)G~t@#rK|(wj(ei?K-2l_aFdbCjfd(0N5=6{1X6I90A~jNxDDe zkZDW=MxAJd=nKHCV+Jv`DoxB-lolZm^8x`7aRHD+0;n_At^zC2s=2Caljr?_Z%8 z1V9%T02mtzn_y!U5ERr?0T>%PKv0m~ z0x(LEH+7XX!zEgL=K`j)Pgn_nz!m@yK0!fX1z;2e!h+YvOs;(!_*VcMl37sTk^pd4 z0CbZ8Of))4P!Ku+&`AO?PniHDs9-~V6o65RtdHL35!K(lfFFoCWiMTaV4;NQ@>mcJ z+9euHHhI>pG=GF|5|OjQRf$$;Lljbp73D%gG>Z_nsl>SFh+e0-er_AhDTEkww}f~* zPUEKe9(}fNKnZ2lIq1=H1Pvl3MCl=Dlpg|dPD*k?qZ|{6OX2zuGppJCJrcUpfbdEU zxr#y?(nt^*X=Dn75K5>}FjT88U8!1Og>y(Y9>^5B&C#N-5RXhb}|gqxU=71B`?8tzOVjHj#O zk~2sUKF5pCrntYv8bRL^Bp)Cs=!ArDr%t zL?jPM!Dnn;xu5WFm|3o;j(}I`G+0qFBIX zMRL&pX832p3fzRd-&Ugydf}jhlv`jIeHJ+)JSL?>M0jLes@9yzDlR8s={%!QK0dHpyJEX5L^|vvanrGXlK3!UB&Z^gg?yYKd z?ykT6e6xUIeUE*0AZ4KQXvC`GrrT%dC7fy8aC2Md@Aakx+O}CDx1Me9s4-*n@Q4+6 zR?iKnyD6i2Nb>{VJepf&(x?j$?cD}0Nv@Ekn-Vp_-Zgpg=4zH@cOpvs*7dEkH|zAy zao+tS_uEUgemdSIt?sNgjenZ@{Os_-Iq$8A|6*U}x^a_M+}v(^y-e?M*^A>#bbfRE zrLZ$YmoDxbn&U-!H4wMf2eb& z;=O=?nSYdBQzCeNy%MMVc1|>0E??p8_=z`OU3{f{g}sM6MxE|H=i_P-XU5H0*{*wu zBOUwHYZtxMJbKXa(+QJTR$f$n@;9$)UFpJ`X3la@gA+-JJ=Ec;GNg+b|3t+IHfz*->fn&NLc`4ZQ;q zCwpY3rY1Q@&=;%q(#Nk|dG8e^Mw%EI=wytPu5lyq!S8&E0D3+lU2Ar8^>!vE49(Em z^ti8~35~$l!4n6EB_|{~>Fe2UmNu>rYKPeHV3!tml5yuz>u>=FE^PEw?Jz1A8CnP4 zMMo2N^~fMrOe~P5aj6~MN~~tw($IwV@jx@SK>&MTnD^2e@L>s?)vXXI>NIL`3)20S zZqx!N9_CbkBioho-Y|GF+>URV7n$Ihb2^ki)Vi$y(NC}aIQF-^=hf=ozJ6%BUxzv+ z17fNUdE@Y-_g}JadDdj){i*wIwyqvj@njeCx)Y}!H`%aq?dtWb)~!jHKQjKcA#u*Z zw=Q45x;XDivHoX^f0198=kvHJeY-4w6GPoD7*HKd>^tA1mpOaCkksQs^_I8n_aoc1 zxoY3>u-=Z8aW$KdKXPH;yh;sTzgM@+m-Xt^wnwGbzJ8$Y73YkENe4A0+ixm<_w$Wy zS1&b3wyoqp>)ed>i8a>!T%ywO4Q1MwEZ!{f?v6Hz+qYi)>+8&*t=Z>y{jnnb`K>=% zeYGo~?o{LHV)ep?+CP7z_>e4p*VEr#iQH}7v*X(C@9wpj7TxQ@vFMCvqY_W#-h0}+ zPo?&QCN&C*tw`XpA>Be6V$Bo>7=KH)(_l{iKU0xTsGo(Sn$yravSIN1Z z{GYu?Z+-vrZ+9pB+&{&(eO)cx?gNXPu3h;}&())vHd*j+IQ{){!TZ9~x81IKx>LjAt*gxL_Rg)GE4%OMYSyl{=s?cF*+HRSPM;As;?drE zrB{3wS7*cb#xhMNHCgKWw>&L&tkW+?XAHUe_+)tKzJ(n(%y}_)(w0h}J&LP- zFK*E1Q{FjTztV*DP0K$ueDZxn%$s8Zewx;KUB}QVj=84IgC>2gv-?#onfGMHtFb!{ z*ZO?eu>P^fKAn5PI=)z&mLJ^RynD%mHD#{$3Vi+SffB__-S56>q2DJj>R%nWvU~T4 zF|RggA6RF0=*_wJl9%VjpPY4Qx9v%r#OBRsH=Er5+XK;K*ZeWEMWqR!pNQVv_m{>4 zZpUa(unmVcg1}% zKK0h^zCC_TU2kc2`;)mpY`J#fz14I76O(*xbLOS5oNKClJ2S_!y>FM$o#k(4b^bka z^~c4&nAD)!%IUKPb$!_RPI__G`#!eT0tvq?v%s!v}`%?ClAC|2C zAvoYp_ld2S#h5b#hF0u+`1l>i)Ad<{R(`r^Ro<-NSKhdAYg9~P-_?DCeoFk;qm_=+ zv(64_cJI?g9hz_d@Ut_k`nGZQ`0lqUcc*m>xmk5hx9(Yce&6+Ng)*hSzp^Uee(vgR zhf`)=9yZ|RuSf1`e%|_I+>f`Tv$yT7|7`kzc~Kc9JO8{mD|YVsK|k+Uo}D+OXQiB8 zZza>}9jzKHn%?g~Tiv1kTaU!tv5bycH)KRq)Si;9(q=Snvh}a6v$k1A z%H+1o~Unsvh{#Bt(~isY?0Et@~N@f=r&=&RnE?^ zR+<;Qtk>bl(YroA@UZQSZ6|Io9@#nLk}+yjrt`*whpo>1-0V@;>63No%M)`3eYbwh zv|StZIp3#z*0<@+;g6zYw{Jg{y*2iS#oHr0#U_uOmNlbg%*~H>{F&W#{lVk?T6KAt zSnkQL&U2nL-ZT4F?`AJ=8XtD9LWhf6u5X(#}@oF{zoEKlqQSvEh~f{x&V>y%qD@-urOin!wclt)KUZvQ>+HW89HF6RST9 z@{c-a8r5OUiE6)}eBp(^ZcJM;@5%T^)u;C_-l;|J{SROHYU~HAM=p4Ic+&RlpiyPV zbUbnQ%FEyNdDiLaiJ=3s|6KU6=JNCxFLxMl=Dn<%;pLjeS8kg(q4m=#BR(8e^+?>) zei_APJ?_@{LK*)(S=;iCbn4l(Vb>e|yI%h0r(YkmUKtmE_@w~zkGV;|UueF3{Kn|a z)ZczxdEuptpM{=&d@}WMcIu1SkE%GXgeB|l`aM{9OWS92>F-86vUe? z)!_@5Zhjt9vQbZ6!28{k>|eF$7rAosNdI>#P1s+@c)Q%=8h@R-a`5UWtNVO>H(>OI zl8yWBUe&*CjYD~hmwy-gtmncG!M#G&aQTJ|bdK}ZX)vgzI3|rZ<(YsgoU0v(g@EUx$ z`lJ87)IV#zmDxWxD7R12(;sRZyUuCy!B0DO|NKMd{&`n6P5d(E-6PdY)o4?*+_ZH! zN`ClZr++2nzT0&3!I*#7_!<(XTIY9mFRHq~)yT)6 z>^}AOfMxHe3_XxFqICIf!TTOv?^b>J@sycCWkcWXu&CFRi}m_<{XFFT&ywszkIn5n zuIiHV8dFfs*Y5V|IBwWyCvNl%dETY+x^e6OdTu)Q$&_tzY5w8zq| z?3VCm(Vb?s&Z#ri+%&0$?$y9z@g2`>UQlU6&V=U~<9^=!TVzzL@y%X+7PGe6(cFY@ z-~Z@%Zi{v|g2qg5S8MOi4U>Ot)pGlvwryL3ZNVER>HHr|O?$J&+olNnq4g1$pIMG) z|5xssk ze(4YyU#_t>#cZ=Y;kdU&|srD~V+ zil1IFWvuRlFORnwIn)`~aow&5}^@vHmG<}Q{E1HV?->$y$ zYrj9s&R@EunE$>e#;}z7A13rXol>S^yYeM(Cq{obW|n!t!}W9a&RWo6{97Y~o|XRS z{Fd|i&Ew?tyl0|mFy%jslinm6z3i{&waT~j^NZZ_!p(fdj!tt9!QWOf>NWI7EZjd@ ztHpxHZie8myLf(R-9SIuWaS*5{{GuJy$zF16(8o2e>-Qw!dA^^#sk0n96m^A!E&C- z!#O(^ne3d=XvXs-e>;aSb^h%fUZGW=v*gp(iq*A>54QYGo(X@}=Wl!#eE37%)`ok} z>T|T%R-MCZ=#qUIX>nMAq`CfVQumY;tUcT-5!Yq}ZFAsn2StQyV_R6QZQ58Z`nF*X zLt8^zTR0-5x0`M4!%X3A!@}EGT55}C!TPC3;ut68fh&39(lT6`f%nqPdQEU}yQub> F{|4!oQJeq( literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html new file mode 100644 index 000000000..e6d4cc8d8 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html @@ -0,0 +1,49 @@ + + + + + +
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse volutpat feugiat massa vitae congue. + +
+Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed non blandit nisl, +id scelerisque sem. +
+Pellentesque sagittis maximus elit et eleifend. Mauris vitae eros quis eros bibendum bibendum. +Cras sed massa lectus. Suspendisse mollis mi id tortor commodo, in semper eros gravida. Integer pulvinar nulla urna, +vel sagittis leo maximus id. Nam sit amet arcu non justo molestie interdum. Phasellus gravida nisi ut bibendum feugiat. +
+In vitae nulla nec sapien imperdiet interdum eget et sem. Aenean ultricies augue vitae quam viverra, et +maximus tortor eleifend. +
+Duis quis venenatis turpis, eu sollicitudin magna. Proin ullamcorper sem vitae sem facilisis +luctus. Mauris sed lectus ac nulla placerat pellentesque ut vel diam. Sed vulputate ornare sem vel placerat. +
+
+
+Nam auctor libero ante, et volutpat arcu dictum ac. +Class aptent taciti sociosqu ad litora torquent per conubia nostra, +per inceptos himenaeos. Nullam non sodales augue, in efficitur turpis. Duis maximus a orci in consequat. +Mauris luctus nec ex et volutpat. Nullam mollis luctus ultrices. Sed malesuada porttitor dui et blandit. +Fusce ultrices metus sem, sed vehicula sem hendrerit vel. Phasellus sed turpis eget urna luctus sagittis in eget mauris. +Vivamus rhoncus cursus lectus vel commodo. Nunc vulputate commodo porta. Nunc eget augue nec nisl hendrerit tincidunt sit +amet sed urna. +
+ + diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-nested-unbalanced.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-nested-unbalanced.html new file mode 100644 index 000000000..1f19e45ce --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-nested-unbalanced.html @@ -0,0 +1,56 @@ + + + + + +
+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse volutpat feugiat massa vitae congue. +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed non blandit nisl, +id scelerisque sem. +

+

+Pellentesque sagittis maximus elit et eleifend. Mauris vitae eros quis eros bibendum bibendum. +Cras sed massa lectus. Suspendisse mollis mi id tortor commodo, in semper eros gravida. Integer pulvinar nulla urna, +vel sagittis leo maximus id. Nam sit amet arcu non justo molestie interdum. Phasellus gravida nisi ut bibendum feugiat. +

+
    +
  • One
  • +
  • Two
  • +
  • Three
  • +
  • Four
  • +
  • Five
  • +
  • Vivamus rhoncus cursus lectus vel commodo. Nunc vulputate commodo porta. Nunc eget augue nec nisl hendrerit tincidunt sit +amet sed urna.
  • +
+

+In vitae nulla nec sapien imperdiet interdum eget et sem. +

+

+Aenean ultricies augue vitae quam viverra, et maximus tortor eleifend. Duis quis venenatis turpis, eu sollicitudin magna. +Proin ullamcorper sem vitae sem facilisis luctus. Mauris sed lectus ac nulla placerat pellentesque ut vel diam. +Sed vulputate ornare sem vel placerat. +

+

+Nam auctor libero ante, et volutpat arcu dictum ac. Class aptent taciti sociosqu ad litora torquent per conubia nostra, +per inceptos himenaeos. Nullam non sodales augue, in efficitur turpis. Duis maximus a orci in consequat. +Mauris luctus nec ex et volutpat. Nullam mollis luctus ultrices. Sed malesuada porttitor dui et blandit. +

+
+ + diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-simple-unbalanced.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-simple-unbalanced.html new file mode 100644 index 000000000..bf9fbdf42 --- /dev/null +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-simple-unbalanced.html @@ -0,0 +1,38 @@ + + + + + +
+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse volutpat feugiat massa vitae congue. +Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed non blandit nisl, +id scelerisque sem. Pellentesque sagittis maximus elit et eleifend. Mauris vitae eros quis eros bibendum bibendum. +Cras sed massa lectus. Suspendisse mollis mi id tortor commodo, in semper eros gravida. Integer pulvinar nulla urna, +vel sagittis leo maximus id. Nam sit amet arcu non justo molestie interdum. Phasellus gravida nisi ut bibendum feugiat. +In vitae nulla nec sapien imperdiet interdum eget et sem. Aenean ultricies augue vitae quam viverra, et +maximus tortor eleifend. Duis quis venenatis turpis, eu sollicitudin magna. Proin ullamcorper sem vitae sem facilisis +luctus. Mauris sed lectus ac nulla placerat pellentesque ut vel diam. Sed vulputate ornare sem vel placerat. +Nam auctor libero ante, et volutpat arcu dictum ac. Class aptent taciti sociosqu ad litora torquent per conubia nostra, +per inceptos himenaeos. Nullam non sodales augue, in efficitur turpis. Duis maximus a orci in consequat. +Mauris luctus nec ex et volutpat. Nullam mollis luctus ultrices. Sed malesuada porttitor dui et blandit. +Fusce ultrices metus sem, sed vehicula sem hendrerit vel. Phasellus sed turpis eget urna luctus sagittis in eget mauris. +Vivamus rhoncus cursus lectus vel commodo. Nunc vulputate commodo porta. Nunc eget augue nec nisl hendrerit tincidunt sit +amet sed urna. +
+ + diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java index 684ee4bce..4be1efb50 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java @@ -614,4 +614,31 @@ public void testJustifySpaceAtEnd() throws IOException { assertTrue(vtester.runTest("text-justify-space-at-end", WITH_COLLAPSED_LINE_BREAKER)); } + /** + * Tests that flowing columns containing only text in unbalanced mode + * are correctly laid out. + */ + @Test + public void testColumnsSimpleUnbalanced() throws IOException { + assertTrue(run("columns-simple-unbalanced")); + } + + /** + * Tests columns with nested content such as paragraphs, lists and span. + */ + @Test + @Ignore // Broken because it doesn't treat grandchildren as break opportunities. + public void testColumnsNestedUnbalanced() throws IOException { + assertTrue(run("columns-nested-unbalanced")); + } + + /** + * Tests columns containing floated and clear elements. + */ + @Test + @Ignore // Crashed with NPE in LayoutUtil::layoutFloated method. + public void testColumnsFloatsUnbalanced() throws IOException { + assertTrue(run("columns-floats-unbalanced")); + } + } From 91f6498df96532dfd3990ef9ed8c47965792c673 Mon Sep 17 00:00:00 2001 From: danfickle Date: Wed, 24 Jul 2019 18:23:35 +1000 Subject: [PATCH 2/6] Get rid of tabs in the flowing column implementation. So it can be worked with easier. --- .../com/openhtmltopdf/layout/BoxBuilder.java | 2 +- .../render/FlowingColumnBox.java | 56 ++--- .../render/FlowingColumnContainerBox.java | 223 +++++++++--------- 3 files changed, 141 insertions(+), 140 deletions(-) diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java index 999037329..91b30be22 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/BoxBuilder.java @@ -1154,7 +1154,7 @@ private static void createChildren( child.setElement(element); if (style.hasColumns() && c.isPrint()) { - FlowingColumnContainerBox cont = (FlowingColumnContainerBox) child; + FlowingColumnContainerBox cont = (FlowingColumnContainerBox) child; cont.setOnlyChild(c, new FlowingColumnBox(cont)); cont.getChild().setStyle(style.createAnonymousStyle(IdentValue.BLOCK)); cont.getChild().setElement(element); diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnBox.java index 81e4c831f..82a399c62 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnBox.java @@ -1,34 +1,34 @@ package com.openhtmltopdf.render; public class FlowingColumnBox extends BlockBox { - private final Box parent; - private int _colWidth; - - public FlowingColumnBox(Box parent) { - this.parent = parent; - } - - @Override - public boolean isFlowingColumnBox() { - return true; - } - - @Override - public int getWidth() { - return this._colWidth; - } - - @Override - public int getContentWidth() { - return this._colWidth; - } + private final Box parent; + private int _colWidth; - @Override - public Box getParent() { - return parent; - } + public FlowingColumnBox(Box parent) { + this.parent = parent; + } - public void setColumnWidth(int columnWidth) { - this._colWidth = columnWidth; - } + @Override + public boolean isFlowingColumnBox() { + return true; + } + + @Override + public int getWidth() { + return this._colWidth; + } + + @Override + public int getContentWidth() { + return this._colWidth; + } + + @Override + public Box getParent() { + return parent; + } + + public void setColumnWidth(int columnWidth) { + this._colWidth = columnWidth; + } } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java index a8a9f7d72..161d28a33 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java @@ -8,115 +8,116 @@ import com.openhtmltopdf.layout.LayoutContext; public class FlowingColumnContainerBox extends BlockBox { - private FlowingColumnBox _child; - - private int findPageIndex(List pages, int y) { - int idx = 0; - for (PageBox page : pages) { - if (y >= page.getTop() && y <= page.getBottom()) { - return idx; - } - idx++; - } - return idx - 1; - } - - private static class ColumnPosition { - private final int copyY; // Absolute, What y position starts the column in the long column block. - private final int pasteY; // Absolute, What y position starts the column in the flowing column block for final render. - private final int maxColHeight; // Absolute, Maximum bottom of the column. - private final int pageIdx; - - private ColumnPosition(int copyY, int pasteY, int maxColHeight, int pageIdx) { - this.copyY = copyY; - this.pasteY = pasteY; - this.maxColHeight = maxColHeight; - this.pageIdx = pageIdx; - } - } - - private int adjust(LayoutContext c, Box child, int colGap, int colWidth, int columnCount, int xStart) { - int startY = this.getAbsY(); - List pages = c.getRootLayer().getPages(); - - int pageIdx = findPageIndex(pages, startY); - int colStart = startY; - int colHeight = pages.get(pageIdx).getBottom(); - int colIdx = 0; - int finalHeight = 0; - - List cols = new ArrayList(); - cols.add(new ColumnPosition(0, startY, colHeight, pageIdx)); - - for (Object chld : child.getChildren()) { - Box ch = (Box) chld; - - int yAdjust = cols.get(colIdx).pasteY - cols.get(colIdx).copyY; - int yProposedFinal = ch.getY() + yAdjust; - - if (yProposedFinal + ch.getHeight() > cols.get(colIdx).maxColHeight) { - int newColIdx = colIdx + 1; - int newPageIdx = newColIdx % columnCount == 0 ? cols.get(colIdx).pageIdx + 1 : cols.get(colIdx).pageIdx; - - if (newPageIdx >= pages.size()) { - c.getRootLayer().addPage(c); - } - - int pasteY = newColIdx % columnCount == 0 ? pages.get(newPageIdx).getTop() : cols.get(colIdx).pasteY; - - cols.add(new ColumnPosition(ch.getY(), pasteY, pages.get(newPageIdx).getBottom(), newPageIdx)); - colIdx++; - - yAdjust = cols.get(colIdx).pasteY - cols.get(colIdx).copyY; - yProposedFinal = ch.getY() + yAdjust; - } - - ch.setY(yProposedFinal - colStart); - finalHeight = Math.max(yProposedFinal - colStart + ch.getHeight(), finalHeight); - - int xAdjust = ((colIdx % columnCount) * colWidth) + ((colIdx % columnCount) * colGap); - ch.setX(ch.getX() + xAdjust + xStart); - } - - return finalHeight; - } - - @Override - public void layout(LayoutContext c, int contentStart) { - this.calcDimensions(c); - - int colCount = getStyle().columnCount(); - int colGapCount = colCount - 1; - - float colGap = getStyle().isIdent(CSSName.COLUMN_GAP, IdentValue.NORMAL) ? - getStyle().getLineHeight(c) : /* Use the line height as a normal column gap. */ - getStyle().getFloatPropertyProportionalWidth(CSSName.COLUMN_GAP, getContentWidth(), c); - - float totalGap = colGap * colGapCount; - int colWidth = (int) ((this.getContentWidth() - totalGap) / colCount); - - _child.setContainingLayer(this.getContainingLayer()); - _child.setContentWidth(colWidth); - _child.setColumnWidth(colWidth); - _child.setAbsX(this.getAbsX()); - _child.setAbsY(this.getAbsY()); - - c.setIsPrintOverride(false); - _child.layout(c, contentStart); - c.setIsPrintOverride(null); - - int height = adjust(c, _child, (int) colGap, colWidth, colCount, this.getLeftMBP() + this.getX()); - _child.setHeight(0); - this.setHeight(height); - _child.calcChildLocations(); - } - - public void setOnlyChild(LayoutContext c, FlowingColumnBox child) { - this._child = child; - this.addChild(child); - } - - public FlowingColumnBox getChild() { - return _child; - } + private FlowingColumnBox _child; + + private int findPageIndex(List pages, int y) { + int idx = 0; + for (PageBox page : pages) { + if (y >= page.getTop() && y <= page.getBottom()) { + return idx; + } + idx++; + } + return idx - 1; + } + + private static class ColumnPosition { + private final int copyY; // Absolute, What y position starts the column in the long column block. + private final int pasteY; // Absolute, What y position starts the column in the flowing column block for + // final render. + private final int maxColHeight; // Absolute, Maximum bottom of the column. + private final int pageIdx; + + private ColumnPosition(int copyY, int pasteY, int maxColHeight, int pageIdx) { + this.copyY = copyY; + this.pasteY = pasteY; + this.maxColHeight = maxColHeight; + this.pageIdx = pageIdx; + } + } + + private int adjust(LayoutContext c, Box child, int colGap, int colWidth, int columnCount, int xStart) { + int startY = this.getAbsY(); + List pages = c.getRootLayer().getPages(); + + int pageIdx = findPageIndex(pages, startY); + int colStart = startY; + int colHeight = pages.get(pageIdx).getBottom(); + int colIdx = 0; + int finalHeight = 0; + + List cols = new ArrayList(); + cols.add(new ColumnPosition(0, startY, colHeight, pageIdx)); + + for (Object chld : child.getChildren()) { + Box ch = (Box) chld; + + int yAdjust = cols.get(colIdx).pasteY - cols.get(colIdx).copyY; + int yProposedFinal = ch.getY() + yAdjust; + + if (yProposedFinal + ch.getHeight() > cols.get(colIdx).maxColHeight) { + int newColIdx = colIdx + 1; + int newPageIdx = newColIdx % columnCount == 0 ? cols.get(colIdx).pageIdx + 1 : cols.get(colIdx).pageIdx; + + if (newPageIdx >= pages.size()) { + c.getRootLayer().addPage(c); + } + + int pasteY = newColIdx % columnCount == 0 ? pages.get(newPageIdx).getTop() : cols.get(colIdx).pasteY; + + cols.add(new ColumnPosition(ch.getY(), pasteY, pages.get(newPageIdx).getBottom(), newPageIdx)); + colIdx++; + + yAdjust = cols.get(colIdx).pasteY - cols.get(colIdx).copyY; + yProposedFinal = ch.getY() + yAdjust; + } + + ch.setY(yProposedFinal - colStart); + finalHeight = Math.max(yProposedFinal - colStart + ch.getHeight(), finalHeight); + + int xAdjust = ((colIdx % columnCount) * colWidth) + ((colIdx % columnCount) * colGap); + ch.setX(ch.getX() + xAdjust + xStart); + } + + return finalHeight; + } + + @Override + public void layout(LayoutContext c, int contentStart) { + this.calcDimensions(c); + + int colCount = getStyle().columnCount(); + int colGapCount = colCount - 1; + + float colGap = getStyle().isIdent(CSSName.COLUMN_GAP, IdentValue.NORMAL) ? getStyle().getLineHeight(c) + : /* Use the line height as a normal column gap. */ + getStyle().getFloatPropertyProportionalWidth(CSSName.COLUMN_GAP, getContentWidth(), c); + + float totalGap = colGap * colGapCount; + int colWidth = (int) ((this.getContentWidth() - totalGap) / colCount); + + _child.setContainingLayer(this.getContainingLayer()); + _child.setContentWidth(colWidth); + _child.setColumnWidth(colWidth); + _child.setAbsX(this.getAbsX()); + _child.setAbsY(this.getAbsY()); + + c.setIsPrintOverride(false); + _child.layout(c, contentStart); + c.setIsPrintOverride(null); + + int height = adjust(c, _child, (int) colGap, colWidth, colCount, this.getLeftMBP() + this.getX()); + _child.setHeight(0); + this.setHeight(height); + _child.calcChildLocations(); + } + + public void setOnlyChild(LayoutContext c, FlowingColumnBox child) { + this._child = child; + this.addChild(child); + } + + public FlowingColumnBox getChild() { + return _child; + } } From fa3bb6d0f2415453d1adafdd31d6bc2e351fa1ee Mon Sep 17 00:00:00 2001 From: danfickle Date: Fri, 26 Jul 2019 19:04:13 +1000 Subject: [PATCH 3/6] Implements nested content such as ul, p, img, span inside flowing columns With test. --- .../java/com/openhtmltopdf/render/Box.java | 71 +++++- .../render/FlowingColumnBox.java | 5 - .../render/FlowingColumnContainerBox.java | 203 ++++++++++++++---- .../com/openhtmltopdf/render/LineBox.java | 7 + .../text/columns-nested-unbalanced.pdf | Bin 0 -> 39162 bytes .../html/text/columns-nested-unbalanced.html | 18 +- .../TextVisualRegressionTest.java | 1 - 7 files changed, 242 insertions(+), 63 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-nested-unbalanced.pdf diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java index 996d22dbc..7ad4a8707 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java @@ -32,6 +32,7 @@ import com.openhtmltopdf.layout.LayoutContext; import com.openhtmltopdf.layout.PaintingInfo; import com.openhtmltopdf.layout.Styleable; +import com.openhtmltopdf.render.FlowingColumnContainerBox.ColumnBreakStore; import com.openhtmltopdf.util.XRLog; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -45,6 +46,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.function.Predicate; import java.util.logging.Level; public abstract class Box implements Styleable, DisplayListItem { @@ -1336,15 +1338,66 @@ protected boolean isInitialContainingBlock() { return false; } - public boolean isFlowingColumnBox() { - while (getParent() != null) { - if (getParent().isFlowingColumnBox()) { - return true; - } - } - - return false; - } + /** + * Is this box the first child of its parent? + */ + public boolean isFirstChild() { + return getParent() != null && + getParent().getChildCount() > 0 && + getParent().getChild(0) == this; + } + + /** + * Is this box unbreakable in regards to column break opportunities? + */ + public boolean isTerminalColumnBreak() { + return getChildCount() == 0; + } + + /** + * Creates a list of ancestors by walking up the chain of parent, + * grandparent, etc. Stops when the provided predicate returns false + * or the root box otherwise. + */ + public List ancestorsUntil(Predicate predicate) { + List ancestors = new ArrayList<>(4); + Box parent = this.getParent(); + + while (parent != null && predicate.test(parent)) { + ancestors.add(parent); + parent = parent.getParent(); + } + + return ancestors; + } + + /** + * Get all ancestors, up until the root box. + */ + public List ancestors() { + return ancestorsUntil(unused -> Boolean.TRUE); + } + + /** + * Recursive method to find column break opportunities. + * @param store - use to report break opportunities. + */ + public void findColumnBreakOpportunities(ColumnBreakStore store) { + if (this.isTerminalColumnBreak() && this.isFirstChild()) { + // We report unprocessed ancestor container boxes so that they + // can be moved with the first child. + List ancestors = this.ancestorsUntil(store::checkContainerShouldProcess); + store.addBreak(this, ancestors); + } else if (this.isTerminalColumnBreak()) { + store.addBreak(this, null); + } else { + // This must be a container box so don't add it as a break opportunity. + // Recursively query children for their break opportunities. + for (Box child : getChildren()) { + child.findColumnBreakOpportunities(store); + } + } + } } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnBox.java index 82a399c62..8c2d0309e 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnBox.java @@ -8,11 +8,6 @@ public FlowingColumnBox(Box parent) { this.parent = parent; } - @Override - public boolean isFlowingColumnBox() { - return true; - } - @Override public int getWidth() { return this._colWidth; diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java index 161d28a33..a757527a2 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java @@ -1,7 +1,11 @@ package com.openhtmltopdf.render; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import com.openhtmltopdf.css.constants.CSSName; import com.openhtmltopdf.css.constants.IdentValue; @@ -10,6 +14,7 @@ public class FlowingColumnContainerBox extends BlockBox { private FlowingColumnBox _child; + // FIXME: Inefficient, replace with binary search. private int findPageIndex(List pages, int y) { int idx = 0; for (PageBox page : pages) { @@ -22,10 +27,10 @@ private int findPageIndex(List pages, int y) { } private static class ColumnPosition { - private final int copyY; // Absolute, What y position starts the column in the long column block. + private final int copyY; // Absolute, What y position starts the column in the long column block. private final int pasteY; // Absolute, What y position starts the column in the flowing column block for // final render. - private final int maxColHeight; // Absolute, Maximum bottom of the column. + private final int maxColHeight; // Absolute, Maximum height of the column. private final int pageIdx; private ColumnPosition(int copyY, int pasteY, int maxColHeight, int pageIdx) { @@ -35,50 +40,163 @@ private ColumnPosition(int copyY, int pasteY, int maxColHeight, int pageIdx) { this.pageIdx = pageIdx; } } - - private int adjust(LayoutContext c, Box child, int colGap, int colWidth, int columnCount, int xStart) { - int startY = this.getAbsY(); - List pages = c.getRootLayer().getPages(); - - int pageIdx = findPageIndex(pages, startY); - int colStart = startY; - int colHeight = pages.get(pageIdx).getBottom(); - int colIdx = 0; - int finalHeight = 0; - - List cols = new ArrayList(); - cols.add(new ColumnPosition(0, startY, colHeight, pageIdx)); - - for (Object chld : child.getChildren()) { - Box ch = (Box) chld; - - int yAdjust = cols.get(colIdx).pasteY - cols.get(colIdx).copyY; - int yProposedFinal = ch.getY() + yAdjust; - - if (yProposedFinal + ch.getHeight() > cols.get(colIdx).maxColHeight) { - int newColIdx = colIdx + 1; - int newPageIdx = newColIdx % columnCount == 0 ? cols.get(colIdx).pageIdx + 1 : cols.get(colIdx).pageIdx; - - if (newPageIdx >= pages.size()) { - c.getRootLayer().addPage(c); - } - - int pasteY = newColIdx % columnCount == 0 ? pages.get(newPageIdx).getTop() : cols.get(colIdx).pasteY; - - cols.add(new ColumnPosition(ch.getY(), pasteY, pages.get(newPageIdx).getBottom(), newPageIdx)); - colIdx++; - - yAdjust = cols.get(colIdx).pasteY - cols.get(colIdx).copyY; - yProposedFinal = ch.getY() + yAdjust; + + public static class ColumnBreakOpportunity { + private final Box box; // The box where we can break. + private final List ancestors; // Ancestors of this box which should be moved with it. + + private ColumnBreakOpportunity(Box box, List ancestors) { + this.box = box; + this.ancestors = ancestors; + } + + static ColumnBreakOpportunity of(Box box, List ancestors) { + return new ColumnBreakOpportunity(box, ancestors); + } + + @Override + public String toString() { + return String.valueOf(box); + } + } + + public static class ColumnBreakStore { + // Break opportunity boxes. + private final List breaks = new ArrayList<>(); + // Which container boxes have been processed, so we don't move them twice. + private final Set processedContainers = new HashSet<>(); + + /** + * Add a break opportunity. If this is a break opportunity and a first child, it + * should also add all unprocessed ancestors, so they can be moved with the + * first child. + */ + public void addBreak(Box box, List ancestors) { + breaks.add(ColumnBreakOpportunity.of(box, ancestors)); + } + + /** + * Whether an ancestor box needs to be added to the list of ancestors. + * @return true to process this ancestor (we haven't seen it yet). + */ + public boolean checkContainerShouldProcess(Box container) { + if (container instanceof FlowingColumnContainerBox || + container instanceof FlowingColumnBox) { + return false; } + + return processedContainers.add(container); + } - ch.setY(yProposedFinal - colStart); - finalHeight = Math.max(yProposedFinal - colStart + ch.getHeight(), finalHeight); + @Override + public String toString() { + return breaks.toString(); + } + } + + private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidth, int columnCount, int xStart) { + // At the start of this method we have one long column in child. + // This method works by going through the boxes and adjusting their position + // into the current column. + + final int startY = this.getAbsY(); + final List pages = c.getRootLayer().getPages(); + + // These are all running values that change as we layout our boxes into columns. + int pageIdx = findPageIndex(pages, startY); + int colStart = startY; + int colHeight = pages.get(pageIdx).getBottom() - this.getChild().getAbsY(); + int colIdx = 0; + int finalHeight = 0; + + if (child.getHeight() <= colHeight) { + // We fit in the first column. + return child.getHeight(); + } + + // Recursively find all the column break opportunities (typically line boxes). + ColumnBreakStore store = new ColumnBreakStore(); + child.findColumnBreakOpportunities(store); + + if (store.breaks.isEmpty() || store.breaks.size() == 1) { + // Nothing we can do except overflow. + // The only break is at the start of the first child. + return this.getChild().getHeight(); + } + // Add our first column. + ColumnPosition current = new ColumnPosition(/* copy-from */ colStart, /* copy-to */ colStart, colHeight, pageIdx); + + Collections.sort(store.breaks, + Comparator.comparingInt(brk -> brk.box.getAbsY() + brk.box.getHeight())); + + for (int i = 0; i < store.breaks.size(); i++) { + ColumnBreakOpportunity br = store.breaks.get(i); + ColumnBreakOpportunity nextBr = i < store.breaks.size() - 1 ? store.breaks.get(i + 1) : null; + Box ch = br.box; + + int yAdjust = current.pasteY - current.copyY; + int yProposedFinal = ch.getAbsY() + yAdjust; + ch.setAbsY(yProposedFinal); + + // We need the max height of the column which is the bottom of the current box + // minus the top of the column. + finalHeight = Math.max((yProposedFinal + ch.getHeight()) - current.pasteY, finalHeight); + + // x position should be easy. int xAdjust = ((colIdx % columnCount) * colWidth) + ((colIdx % columnCount) * colGap); - ch.setX(ch.getX() + xAdjust + xStart); + ch.setAbsX(ch.getAbsX() + xAdjust); + + if (br.ancestors != null) { + // We move container ancestors with the first child that is + // a break opportunity. + // EXAMPLE: column box -> p -> ul -> li -> line box + // We would move the p, ul and li on the first line of the first li. + // For the second li we only have to move the parent li as p and ul have + // already been processed. + for (Box ancestor : br.ancestors) { + ancestor.setAbsY(ancestor.getAbsY() + yAdjust); + ancestor.setAbsX(ancestor.getAbsX() + xAdjust); + } + // FIXME: We do not resize or duplicate ancestor container boxes, + // so if user has used border, background color + // or overflow: hidden it will produce incorrect results. + } + + if (ch instanceof LineBox) { + // We do not call this on other kind of boxes as it would undo our work in moving them. + ch.calcChildLocations(); + } + + if (nextBr != null) { + Box next = nextBr.box; + int nextYHeight = next.getAbsY() + yAdjust + next.getHeight() - current.pasteY; + + if (nextYHeight > current.maxColHeight) { + // We have moved past the bottom of the current column. + // Time for a new column. + // FIXME: What if box doesn't fit in new column either? + int newColIdx = colIdx + 1; + + // And possibly a new page. + boolean needNewPage = newColIdx % columnCount == 0; + int newPageIdx = needNewPage ? current.pageIdx + 1 : current.pageIdx; + + if (newPageIdx >= pages.size()) { + c.getRootLayer().addPage(c); + } + + // We need the y top of the new column. + PageBox page = pages.get(newPageIdx); + int pasteY = needNewPage ? page.getTop() : current.pasteY; + int copyY = next.getAbsY(); + + current = new ColumnPosition(copyY, pasteY, page.getBottom() - pasteY, newPageIdx); + colIdx++; + } + } } - + return finalHeight; } @@ -106,10 +224,9 @@ public void layout(LayoutContext c, int contentStart) { _child.layout(c, contentStart); c.setIsPrintOverride(null); - int height = adjust(c, _child, (int) colGap, colWidth, colCount, this.getLeftMBP() + this.getX()); + int height = adjustUnbalanced(c, _child, (int) colGap, colWidth, colCount, this.getLeftMBP() + this.getX()); _child.setHeight(0); this.setHeight(height); - _child.calcChildLocations(); } public void setOnlyChild(LayoutContext c, FlowingColumnBox child) { diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java index 597f7a7f9..b2a0f96f0 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java @@ -42,6 +42,7 @@ import com.openhtmltopdf.layout.Layer; import com.openhtmltopdf.layout.LayoutContext; import com.openhtmltopdf.layout.PaintingInfo; +import com.openhtmltopdf.render.FlowingColumnContainerBox.ColumnBreakStore; import com.openhtmltopdf.util.XRRuntimeException; /** @@ -643,6 +644,12 @@ public boolean isLayedOutRTL() { public boolean hasNonTextContent(CssContext c) { return _textDecorations != null && _textDecorations.size() > 0; } + + @Override + public boolean isTerminalColumnBreak() { + // A line box can not be further broken for the purpose of column breaks. + return true; + } } /* diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-nested-unbalanced.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-nested-unbalanced.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1d75ad5b96fba2fd87f446bf33969a0db61fcfcb GIT binary patch literal 39162 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 -
+
+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse volutpat feugiat massa vitae congue. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed non blandit nisl, @@ -29,7 +36,7 @@ Cras sed massa lectus. Suspendisse mollis mi id tortor commodo, in semper eros gravida. Integer pulvinar nulla urna, vel sagittis leo maximus id. Nam sit amet arcu non justo molestie interdum. Phasellus gravida nisi ut bibendum feugiat.

-
    +
    • One
    • Two
    • Three
    • @@ -38,6 +45,7 @@
    • Vivamus rhoncus cursus lectus vel commodo. Nunc vulputate commodo porta. Nunc eget augue nec nisl hendrerit tincidunt sit amet sed urna.
    +Flyingsaucer

    In vitae nulla nec sapien imperdiet interdum eget et sem.

    diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java index 4be1efb50..f5d910cb8 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java @@ -627,7 +627,6 @@ public void testColumnsSimpleUnbalanced() throws IOException { * Tests columns with nested content such as paragraphs, lists and span. */ @Test - @Ignore // Broken because it doesn't treat grandchildren as break opportunities. public void testColumnsNestedUnbalanced() throws IOException { assertTrue(run("columns-nested-unbalanced")); } From 54f096e57b846ae0728d0e770021450aa32fedcb Mon Sep 17 00:00:00 2001 From: danfickle Date: Mon, 29 Jul 2019 21:44:58 +1000 Subject: [PATCH 4/6] Refactoring with the power of lambdas. --- .../com/openhtmltopdf/newtable/TableBox.java | 12 +---- .../com/openhtmltopdf/render/BlockBox.java | 12 +---- .../java/com/openhtmltopdf/render/Box.java | 53 ++++++++++--------- .../openhtmltopdf/render/InlineLayoutBox.java | 11 +--- .../com/openhtmltopdf/render/LineBox.java | 3 +- .../com/openhtmltopdf/util/LambdaUtil.java | 15 ++++++ 6 files changed, 51 insertions(+), 55 deletions(-) create mode 100644 openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LambdaUtil.java diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/newtable/TableBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/newtable/TableBox.java index 04065bf4b..451fc3170 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/newtable/TableBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/newtable/TableBox.java @@ -366,16 +366,8 @@ private int layoutRunningFooter(LayoutContext c) { } private boolean isNeedAnalyzePageBreaks() { - Box b = getParent(); - while (b != null) { - if (b.getStyle().isTable() && b.getStyle().isPaginateTable()) { - return false; - } - - b = b.getParent(); - } - - return true; + Box b = findAncestor(bx -> bx.getStyle().isTable() && bx.getStyle().isPaginateTable()); + return b == null; } private void analyzePageBreaks(LayoutContext c) { diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java index 3e9abacd2..464a04edc 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java @@ -289,11 +289,7 @@ public LineBox getLineBox() { if (! isInline()) { return null; } else { - Box b = getParent(); - while (! (b instanceof LineBox)) { - b = b.getParent(); - } - return (LineBox) b; + return (LineBox) findAncestor(bx -> bx instanceof LineBox); } } @@ -2263,11 +2259,7 @@ protected boolean isInlineBlock() { } public boolean isInMainFlow() { - Box flowRoot = this; - while (flowRoot.getParent() != null) { - flowRoot = flowRoot.getParent(); - } - + Box flowRoot = rootBox(); return flowRoot.isRoot(); } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java index 7ad4a8707..f11ca5aa6 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/Box.java @@ -33,6 +33,7 @@ import com.openhtmltopdf.layout.PaintingInfo; import com.openhtmltopdf.layout.Styleable; import com.openhtmltopdf.render.FlowingColumnContainerBox.ColumnBreakStore; +import com.openhtmltopdf.util.LambdaUtil; import com.openhtmltopdf.util.XRLog; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -1270,16 +1271,7 @@ protected void exportPageBoxText(RenderingContext c, Writer writer, int yPos) th } public boolean isInDocumentFlow() { - Box flowRoot = this; - while (true) { - Box parent = flowRoot.getParent(); - if (parent == null) { - break; - } else { - flowRoot = parent; - } - } - + Box flowRoot = rootBox(); return flowRoot.isRoot(); } @@ -1317,17 +1309,7 @@ protected boolean isMarginAreaRoot() { } public boolean isContainedInMarginBox() { - Box current = this; - while (true) { - Box parent = current.getParent(); - if (parent == null) { - break; - } else { - current = parent; - } - } - - return current.isMarginAreaRoot(); + return rootBox().isMarginAreaRoot(); } public int getEffectiveWidth() { @@ -1359,7 +1341,7 @@ public boolean isTerminalColumnBreak() { * grandparent, etc. Stops when the provided predicate returns false * or the root box otherwise. */ - public List ancestorsUntil(Predicate predicate) { + public List ancestorsWhile(Predicate predicate) { List ancestors = new ArrayList<>(4); Box parent = this.getParent(); @@ -1375,7 +1357,30 @@ public List ancestorsUntil(Predicate predicate) { * Get all ancestors, up until the root box. */ public List ancestors() { - return ancestorsUntil(unused -> Boolean.TRUE); + return ancestorsWhile(LambdaUtil.alwaysTrue()); + } + + /** + * Walks up the ancestor tree to the root testing ancestors agains + * the predicate. + * NOTE: Does not test against the current box (this). + * @return the box for which predicate returned true or null if none found. + */ + public Box findAncestor(Predicate predicate) { + Box parent = getParent(); + + while (parent != null && !predicate.test(parent)) { + parent = parent.getParent(); + } + + return parent; + } + + /** + * Returns the highest ancestor box. May be current box (this). + */ + public Box rootBox() { + return this.getParent() != null ? findAncestor(bx -> bx.getParent() == null) : this; } /** @@ -1386,7 +1391,7 @@ public void findColumnBreakOpportunities(ColumnBreakStore store) { if (this.isTerminalColumnBreak() && this.isFirstChild()) { // We report unprocessed ancestor container boxes so that they // can be moved with the first child. - List ancestors = this.ancestorsUntil(store::checkContainerShouldProcess); + List ancestors = this.ancestorsWhile(store::checkContainerShouldProcess); store.addBreak(this, ancestors); } else if (this.isTerminalColumnBreak()) { store.addBreak(this, null); diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/InlineLayoutBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/InlineLayoutBox.java index 888c15fa7..a71a57ad5 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/InlineLayoutBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/InlineLayoutBox.java @@ -504,11 +504,7 @@ private void addToContentList(List list) { } public LineBox getLineBox() { - Box b = getParent(); - while (! (b instanceof LineBox)) { - b = b.getParent(); - } - return (LineBox)b; + return (LineBox) findAncestor(bx -> bx instanceof LineBox); } public List getElementWithContent() { @@ -873,10 +869,7 @@ protected void restyleChildren(LayoutContext c) { public Box getRestyleTarget() { // Inline boxes may be broken across lines so back out // to the nearest block box - Box result = getParent(); - while (result instanceof InlineLayoutBox) { - result = result.getParent(); - } + Box result = findAncestor(bx -> !(bx instanceof InlineLayoutBox)); return result.getParent(); } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java index b2a0f96f0..5b18bbb28 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/LineBox.java @@ -153,8 +153,7 @@ private void lookForDynamicFunctions(RenderingContext c) { } public boolean isFirstLine() { - Box parent = getParent(); - return parent != null && parent.getChildCount() > 0 && parent.getChild(0) == this; + return super.isFirstChild(); } public void prunePendingInlineBoxes() { diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LambdaUtil.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LambdaUtil.java new file mode 100644 index 000000000..8b08c3846 --- /dev/null +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/util/LambdaUtil.java @@ -0,0 +1,15 @@ +package com.openhtmltopdf.util; + +import java.util.function.Predicate; + +public class LambdaUtil { + private LambdaUtil() { } + + public static Predicate alwaysTrue() { + return (unused) -> true; + } + + public static Predicate alwaysFalse() { + return (unused) -> false; + } +} From 47d7201114ac598125a61858a3de6e00102bedb8 Mon Sep 17 00:00:00 2001 From: danfickle Date: Tue, 30 Jul 2019 14:30:36 +1000 Subject: [PATCH 5/6] Nested floats in flowing columns. --- .../css/style/CalculatedStyle.java | 6 +- .../openhtmltopdf/layout/FloatManager.java | 18 +-- .../com/openhtmltopdf/render/BlockBox.java | 4 +- .../openhtmltopdf/render/FloatedBoxData.java | 5 + .../render/FlowingColumnContainerBox.java | 110 ++++++++++++++++-- .../html/text/columns-floats-unbalanced.html | 15 ++- .../TextVisualRegressionTest.java | 2 +- 7 files changed, 133 insertions(+), 27 deletions(-) diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java index 6f8fffacb..1b6d34f31 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java @@ -943,6 +943,10 @@ public boolean isAutoZIndex() { } public boolean establishesBFC() { + if (hasColumns()) { + return true; + } + FSDerivedValue value = valueByName(CSSName.POSITION); if (value instanceof FunctionValue) { // running(header) @@ -1031,7 +1035,7 @@ public boolean isListItem() { } public boolean hasColumns() { - return !isIdent(CSSName.COLUMN_COUNT, IdentValue.AUTO) && asFloat(CSSName.COLUMN_COUNT) > 1; + return !isIdent(CSSName.COLUMN_COUNT, IdentValue.AUTO) && asFloat(CSSName.COLUMN_COUNT) > 1; } public int columnCount() { diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/FloatManager.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/FloatManager.java index 891c54c4b..fab981133 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/FloatManager.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/layout/FloatManager.java @@ -37,8 +37,8 @@ * non-floated (block) boxes. */ public class FloatManager { - private static final int LEFT = 1; - private static final int RIGHT = 2; + public static final int LEFT = 1; + public static final int RIGHT = 2; /* Lazily created for performance. */ private List _leftFloats = Collections.emptyList(); @@ -124,7 +124,7 @@ private void position(CssContext cssCtx, BlockFormattingContext bfc, } } - private List getFloats(int direction) { + public List getFloats(int direction) { return direction == LEFT ? _leftFloats : _rightFloats; } @@ -435,10 +435,10 @@ public void performFloatOperation(FloatOperation op) { performFloatOperation(op, getFloats(RIGHT)); } - private static class BoxOffset { - private BlockBox _box; - private int _x; - private int _y; + public static class BoxOffset { + private final BlockBox _box; + private final int _x; + private final int _y; public BoxOffset(BlockBox box, int x, int y) { _box = box; @@ -460,8 +460,8 @@ public int getY() { } private static class BoxDistance { - private BlockBox _box; - private int _distance; + private final BlockBox _box; + private final int _distance; public BoxDistance(BlockBox box, int distance) { _box = box; diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java index 464a04edc..9ca07a3df 100755 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/BlockBox.java @@ -926,7 +926,7 @@ private void calcExtraPageClearance(LayoutContext c) { } } - private void addBoxID(LayoutContext c) { + protected void addBoxID(LayoutContext c) { if (! isAnonymous()) { String name = c.getNamespaceHandler().getAnchorName(getElement()); if (name != null) { @@ -1304,7 +1304,7 @@ protected boolean isMayCollapseMarginsWithChildren() { // This will require a rethink if we ever truly layout incrementally // Should only ever collapse top margin and pick up collapsable // bottom margins by looking back up the tree. - private void collapseMargins(LayoutContext c) { + protected void collapseMargins(LayoutContext c) { if (! isTopMarginCalculated() || ! isBottomMarginCalculated()) { recalcMargin(c); RectPropertySet margin = getMargin(c); diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FloatedBoxData.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FloatedBoxData.java index 453a0cb31..af04efc6d 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FloatedBoxData.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FloatedBoxData.java @@ -56,4 +56,9 @@ public int getMarginFromSibling() { public void setMarginFromSibling(int marginFromSibling) { _marginFromSibling = marginFromSibling; } + + @Override + public String toString() { + return String.format("[manager='%s', drawing-layer='%s', margin-from-sibling='%d']", getManager(), getDrawingLayer(), getMarginFromSibling()); + } } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java index a757527a2..834afff34 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java @@ -5,11 +5,17 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.TreeMap; import com.openhtmltopdf.css.constants.CSSName; import com.openhtmltopdf.css.constants.IdentValue; +import com.openhtmltopdf.layout.BlockFormattingContext; +import com.openhtmltopdf.layout.FloatManager; +import com.openhtmltopdf.layout.FloatManager.BoxOffset; import com.openhtmltopdf.layout.LayoutContext; +import com.openhtmltopdf.layout.PersistentBFC; public class FlowingColumnContainerBox extends BlockBox { private FlowingColumnBox _child; @@ -27,18 +33,26 @@ private int findPageIndex(List pages, int y) { } private static class ColumnPosition { + private final int columnIndex; private final int copyY; // Absolute, What y position starts the column in the long column block. private final int pasteY; // Absolute, What y position starts the column in the flowing column block for // final render. private final int maxColHeight; // Absolute, Maximum height of the column. private final int pageIdx; - private ColumnPosition(int copyY, int pasteY, int maxColHeight, int pageIdx) { + private ColumnPosition(int columnIndex, int copyY, int pasteY, int maxColHeight, int pageIdx) { + this.columnIndex = columnIndex; this.copyY = copyY; this.pasteY = pasteY; this.maxColHeight = maxColHeight; this.pageIdx = pageIdx; } + + @Override + public String toString() { + return String.format("[index='%d', copyY='%d', pasteY='%d', maxColHeight='%d', pageIdx='%d']", + columnIndex, copyY, pasteY, maxColHeight, pageIdx); + } } public static class ColumnBreakOpportunity { @@ -94,6 +108,63 @@ public String toString() { } } + private void layoutFloats(TreeMap columns, List floats, int columnCount, int colWidth, int colGap) { + for (BoxOffset bo : floats) { + BlockBox floater = bo.getBox(); + System.out.println("floater = " + floater); + + ColumnBreakStore store = new ColumnBreakStore(); + floater.findColumnBreakOpportunities(store); + + for (ColumnBreakOpportunity breakOp : store.breaks) { + Map.Entry entry = columns.floorEntry(breakOp.box.getAbsY()); + ColumnPosition column = entry.getValue(); + + int yAdjust = column.pasteY - column.copyY; + int xAdjust = ((column.columnIndex % columnCount) * colWidth) + ((column.columnIndex % columnCount) * colGap); + + reposition(breakOp.box, xAdjust, yAdjust); + + if (breakOp.ancestors != null) { + repositionAncestors(breakOp.ancestors, xAdjust, yAdjust); + } + + if (breakOp.box instanceof LineBox) { + breakOp.box.calcChildLocations(); + } + } + } + } + + private void layoutFloats(TreeMap columnMap, PersistentBFC bfc, int columnCount, int colWidth, int colGap) { + List floatsL = this.getPersistentBFC().getFloatManager().getFloats(FloatManager.LEFT); + List floatsR = this.getPersistentBFC().getFloatManager().getFloats(FloatManager.RIGHT); + + layoutFloats(columnMap, floatsL, columnCount, colWidth, colGap); + layoutFloats(columnMap, floatsR, columnCount, colWidth, colGap); + } + + private void reposition(Box box, int xAdjust, int yAdjust) { + if (box instanceof BlockBox && + ((BlockBox) box).isFloated()) { + box.setX(box.getX() + xAdjust); + box.setY(box.getY() + yAdjust); + } else { + box.setAbsY(box.getAbsY() + yAdjust); + box.setAbsX(box.getAbsX() + xAdjust); + } + } + + private void repositionAncestors(List ancestors, int xAdjust, int yAdjust) { + for (Box ancestor : ancestors) { + reposition(ancestor, xAdjust, yAdjust); + } + + // FIXME: We do not resize or duplicate ancestor container boxes, + // so if user has used border, background color + // or overflow: hidden it will produce incorrect results. + } + private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidth, int columnCount, int xStart) { // At the start of this method we have one long column in child. // This method works by going through the boxes and adjusting their position @@ -102,6 +173,13 @@ private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidt final int startY = this.getAbsY(); final List pages = c.getRootLayer().getPages(); + final boolean haveFloats = + !this.getPersistentBFC().getFloatManager().getFloats(FloatManager.LEFT).isEmpty() || + !this.getPersistentBFC().getFloatManager().getFloats(FloatManager.RIGHT).isEmpty(); + + // We only need the tree map if we have floats. + final TreeMap columnMap = haveFloats ? new TreeMap<>() : null; + // These are all running values that change as we layout our boxes into columns. int pageIdx = findPageIndex(pages, startY); int colStart = startY; @@ -125,8 +203,12 @@ private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidt } // Add our first column. - ColumnPosition current = new ColumnPosition(/* copy-from */ colStart, /* copy-to */ colStart, colHeight, pageIdx); + ColumnPosition current = new ColumnPosition(colIdx, /* copy-from */ colStart, /* copy-to */ colStart, colHeight, pageIdx); + if (haveFloats) { + columnMap.put(colStart, current); + } + // FIXME: Don't sort if we have in order - common case. Collections.sort(store.breaks, Comparator.comparingInt(brk -> brk.box.getAbsY() + brk.box.getHeight())); @@ -154,13 +236,7 @@ private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidt // We would move the p, ul and li on the first line of the first li. // For the second li we only have to move the parent li as p and ul have // already been processed. - for (Box ancestor : br.ancestors) { - ancestor.setAbsY(ancestor.getAbsY() + yAdjust); - ancestor.setAbsX(ancestor.getAbsX() + xAdjust); - } - // FIXME: We do not resize or duplicate ancestor container boxes, - // so if user has used border, background color - // or overflow: hidden it will produce incorrect results. + repositionAncestors(br.ancestors, xAdjust, yAdjust); } if (ch instanceof LineBox) { @@ -191,17 +267,29 @@ private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidt int pasteY = needNewPage ? page.getTop() : current.pasteY; int copyY = next.getAbsY(); - current = new ColumnPosition(copyY, pasteY, page.getBottom() - pasteY, newPageIdx); + current = new ColumnPosition(newColIdx, copyY, pasteY, page.getBottom() - pasteY, newPageIdx); + if (haveFloats) { + columnMap.put(copyY, current); + } colIdx++; } } } + if (haveFloats) { + layoutFloats(columnMap, this.getPersistentBFC(), columnCount, colWidth, colGap); + } + return finalHeight; } @Override public void layout(LayoutContext c, int contentStart) { + BlockFormattingContext bfc = new BlockFormattingContext(this, c); + c.pushBFC(bfc); + + addBoxID(c); + this.calcDimensions(c); int colCount = getStyle().columnCount(); @@ -227,6 +315,8 @@ public void layout(LayoutContext c, int contentStart) { int height = adjustUnbalanced(c, _child, (int) colGap, colWidth, colCount, this.getLeftMBP() + this.getX()); _child.setHeight(0); this.setHeight(height); + + c.popBFC(); } public void setOnlyChild(LayoutContext c, FlowingColumnBox child) { diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html index e6d4cc8d8..b4221243b 100644 --- a/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html @@ -10,7 +10,7 @@ margin: 0; padding: 0; } -div { +div.cols { text-align: justify; column-count: 3; column-fill: auto; /* As opposed to balance */ @@ -18,17 +18,24 @@ -
    +
    +
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse volutpat feugiat massa vitae congue. -
    +
    +

    Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed non blandit nisl, id scelerisque sem. +

    +
      +
    1. One
    2. +
    3. Two
    4. +
    Pellentesque sagittis maximus elit et eleifend. Mauris vitae eros quis eros bibendum bibendum. Cras sed massa lectus. Suspendisse mollis mi id tortor commodo, in semper eros gravida. Integer pulvinar nulla urna, vel sagittis leo maximus id. Nam sit amet arcu non justo molestie interdum. Phasellus gravida nisi ut bibendum feugiat. -
    +
    In vitae nulla nec sapien imperdiet interdum eget et sem. Aenean ultricies augue vitae quam viverra, et maximus tortor eleifend.
    diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java index f5d910cb8..c090438a4 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java @@ -635,7 +635,7 @@ public void testColumnsNestedUnbalanced() throws IOException { * Tests columns containing floated and clear elements. */ @Test - @Ignore // Crashed with NPE in LayoutUtil::layoutFloated method. + @Ignore // Basics done but needs more work about breaking on appropriate locations in floats. public void testColumnsFloatsUnbalanced() throws IOException { assertTrue(run("columns-floats-unbalanced")); } From 364626d7c071d25e7012370be80258912fb451ed Mon Sep 17 00:00:00 2001 From: danfickle Date: Sat, 3 Aug 2019 18:54:19 +1000 Subject: [PATCH 6/6] Explicit column breaks plus more work on nested floats in columns. Includes test. --- .../openhtmltopdf/css/constants/CSSName.java | 70 +++++++++--------- .../css/constants/IdentValue.java | 27 +++---- .../property/PrimitivePropertyBuilders.java | 18 +++++ .../css/style/CalculatedStyle.java | 8 ++ .../render/FlowingColumnContainerBox.java | 16 ++-- .../text/columns-floats-unbalanced.pdf | Bin 0 -> 40536 bytes .../html/text/columns-floats-unbalanced.html | 37 ++++++--- .../TextVisualRegressionTest.java | 2 +- 8 files changed, 106 insertions(+), 72 deletions(-) create mode 100644 openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-floats-unbalanced.pdf diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/CSSName.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/CSSName.java index 732e266bf..5f5a230c1 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/CSSName.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/CSSName.java @@ -20,10 +20,10 @@ */ package com.openhtmltopdf.css.constants; +import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.TreeMap; - +import java.util.Objects; import com.openhtmltopdf.css.parser.CSSErrorHandler; import com.openhtmltopdf.css.parser.CSSParser; import com.openhtmltopdf.css.parser.PropertyValue; @@ -55,7 +55,7 @@ * * @author Patrick Wright */ -public final class CSSName implements Comparable { +public final class CSSName implements Comparable { /** * marker var, used for initialization */ @@ -115,12 +115,12 @@ public final class CSSName implements Comparable { /** * Map of all CSS properties */ - private static final Map ALL_PROPERTY_NAMES = new TreeMap(); + private static final Map ALL_PROPERTY_NAMES = new HashMap<>(); /** * Map of all non-shorthand CSS properties */ - private static final Map ALL_PRIMITIVE_PROPERTY_NAMES = new TreeMap(); + private static final Map ALL_PRIMITIVE_PROPERTY_NAMES = new HashMap<>(); /** * Unique CSSName instance for CSS2 property. @@ -915,6 +915,24 @@ public final class CSSName implements Comparable { INHERITS, new PrimitivePropertyBuilders.PageBreakInside() ); + + public final static CSSName BREAK_AFTER = + addProperty( + "break-after", + PRIMITIVE, + "auto", + NOT_INHERITED, + new PrimitivePropertyBuilders.BreakAfter() + ); + + public final static CSSName BREAK_BEFORE = + addProperty( + "break-before", + PRIMITIVE, + "auto", + NOT_INHERITED, + new PrimitivePropertyBuilders.BreakBefore() + ); /** * Unique CSSName instance for CSS2 property. @@ -1815,24 +1833,6 @@ public static int countCSSPrimitiveNames() { return ALL_PRIMITIVE_PROPERTY_NAMES.size(); } - /** - * Iterator of ALL CSS 2 visual property names. - * - * @return Returns - */ - public static Iterator allCSS2PropertyNames() { - return ALL_PROPERTY_NAMES.keySet().iterator(); - } - - /** - * Iterator of ALL primitive (non-shorthand) CSS 2 visual property names. - * - * @return Returns - */ - public static Iterator allCSS2PrimitivePropertyNames() { - return ALL_PRIMITIVE_PROPERTY_NAMES.keySet().iterator(); - } - /** * Returns true if the named property inherits by default, according to the * CSS2 spec. @@ -1877,8 +1877,7 @@ public static PropertyBuilder getPropertyBuilder(CSSName cssName) { * @return The byPropertyName value */ public static CSSName getByPropertyName(String propName) { - - return (CSSName) ALL_PROPERTY_NAMES.get(propName); + return ALL_PROPERTY_NAMES.get(propName); } public static CSSName getByID(int id) { @@ -1927,10 +1926,10 @@ private static synchronized CSSName addProperty( } static { - Iterator iter = ALL_PROPERTY_NAMES.values().iterator(); + Iterator iter = ALL_PROPERTY_NAMES.values().iterator(); ALL_PROPERTIES = new CSSName[ALL_PROPERTY_NAMES.size()]; while (iter.hasNext()) { - CSSName name = (CSSName) iter.next(); + CSSName name = iter.next(); ALL_PROPERTIES[name.FS_ID] = name; } } @@ -1941,8 +1940,8 @@ public void error(String uri, String message) { XRLog.cssParse("(" + uri + ") " + message); } }); - for (Iterator i = ALL_PRIMITIVE_PROPERTY_NAMES.values().iterator(); i.hasNext(); ) { - CSSName cssName = (CSSName)i.next(); + for (Iterator i = ALL_PRIMITIVE_PROPERTY_NAMES.values().iterator(); i.hasNext(); ) { + CSSName cssName = i.next(); if (cssName.initialValue.charAt(0) != '=' && cssName.implemented) { PropertyValue value = parser.parsePropertyValue( cssName, StylesheetInfo.USER_AGENT, cssName.initialValue); @@ -1959,14 +1958,14 @@ public void error(String uri, String message) { } } - //Assumed to be consistent with equals because CSSName is in essence an enum - public int compareTo(Object object) { - if (object == null) throw new NullPointerException();//required by Comparable - return FS_ID - ((CSSName) object).FS_ID;//will throw ClassCastException according to Comparable if not a CSSName + // Assumed to be consistent with equals because CSSName is in essence an enum + @Override + public int compareTo(CSSName object) { + Objects.requireNonNull(object); + return this.FS_ID - object.FS_ID; } - // FIXME equals, hashcode - + @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof CSSName)) return false; @@ -1976,6 +1975,7 @@ public boolean equals(Object o) { return FS_ID == cssName.FS_ID; } + @Override public int hashCode() { return FS_ID; } diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/IdentValue.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/IdentValue.java index 383ed3688..564af3928 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/IdentValue.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/constants/IdentValue.java @@ -54,12 +54,9 @@ */ public class IdentValue implements FSDerivedValue { private static int maxAssigned = 0; + private static final Map ALL_IDENT_VALUES = new HashMap<>(); - /** - * Description of the Field - */ private final String ident; - public final int FS_ID; public final static IdentValue ABSOLUTE = addValue("absolute"); @@ -254,11 +251,10 @@ public class IdentValue implements FSDerivedValue { public static final IdentValue BORDER_BOX = addValue("border-box"); public static final IdentValue CONTENT_BOX = addValue("content-box"); - - /** - * Description of the Field + /* + * Column break. */ - private static Map ALL_IDENT_VALUES; + public static final IdentValue COLUMN = addValue("column"); /** * Constructor for the IdentValue object @@ -276,6 +272,7 @@ private IdentValue(String ident) { * * @return a string representation of the object. */ + @Override public String toString() { return ident; } @@ -290,22 +287,19 @@ public String toString() { * @return see desc. */ public static IdentValue getByIdentString(String ident) { - IdentValue val = (IdentValue) ALL_IDENT_VALUES.get(ident); + IdentValue val = ALL_IDENT_VALUES.get(ident); if (val == null) { throw new XRRuntimeException("Ident named " + ident + " has no IdentValue instance assigned to it."); } return val; } - /** - * TODO: doc - */ public static boolean looksLikeIdent(String ident) { - return (IdentValue) ALL_IDENT_VALUES.get(ident) != null; + return ALL_IDENT_VALUES.get(ident) != null; } public static IdentValue valueOf(String ident) { - return (IdentValue)ALL_IDENT_VALUES.get(ident); + return ALL_IDENT_VALUES.get(ident); } public static int getIdentCount() { @@ -318,10 +312,7 @@ public static int getIdentCount() { * @param ident The feature to be added to the Value attribute * @return Returns */ - private final static synchronized IdentValue addValue(String ident) { - if (ALL_IDENT_VALUES == null) { - ALL_IDENT_VALUES = new HashMap(); - } + private final static IdentValue addValue(String ident) { IdentValue val = new IdentValue(ident); ALL_IDENT_VALUES.put(ident, val); return val; diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitivePropertyBuilders.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitivePropertyBuilders.java index 3f238294b..c41cb6b34 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitivePropertyBuilders.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/parser/property/PrimitivePropertyBuilders.java @@ -1303,7 +1303,25 @@ protected BitSet getAllowed() { return ALLOWED; } } + + public static class BreakBefore extends SingleIdent { + private static final BitSet ALLOWED = setFor( + new IdentValue[] { IdentValue.AUTO, IdentValue.COLUMN }); + + protected BitSet getAllowed() { + return ALLOWED; + } + } + public static class BreakAfter extends SingleIdent { + private static final BitSet ALLOWED = setFor( + new IdentValue[] { IdentValue.AUTO, IdentValue.COLUMN }); + + protected BitSet getAllowed() { + return ALLOWED; + } + } + public static class Page extends AbstractPropertyBuilder { public List buildDeclarations( CSSName cssName, List values, int origin, boolean important, boolean inheritAllowed) { diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java index 1b6d34f31..6bad460a6 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/css/style/CalculatedStyle.java @@ -1095,6 +1095,14 @@ public boolean isForcePageBreakAfter() { return val == IdentValue.ALWAYS || val == IdentValue.LEFT || val == IdentValue.RIGHT; } + + public boolean isColumnBreakBefore() { + return isIdent(CSSName.BREAK_BEFORE, IdentValue.COLUMN); + } + + public boolean isColumnBreakAfter() { + return isIdent(CSSName.BREAK_AFTER, IdentValue.COLUMN); + } public boolean isAvoidPageBreakInside() { return isIdent(CSSName.PAGE_BREAK_INSIDE, IdentValue.AVOID); diff --git a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java index 834afff34..88c12f824 100644 --- a/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java +++ b/openhtmltopdf-core/src/main/java/com/openhtmltopdf/render/FlowingColumnContainerBox.java @@ -111,7 +111,6 @@ public String toString() { private void layoutFloats(TreeMap columns, List floats, int columnCount, int colWidth, int colGap) { for (BoxOffset bo : floats) { BlockBox floater = bo.getBox(); - System.out.println("floater = " + floater); ColumnBreakStore store = new ColumnBreakStore(); floater.findColumnBreakOpportunities(store); @@ -210,7 +209,7 @@ private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidt // FIXME: Don't sort if we have in order - common case. Collections.sort(store.breaks, - Comparator.comparingInt(brk -> brk.box.getAbsY() + brk.box.getHeight())); + Comparator.comparingInt(brk -> brk.box.getAbsY() + brk.box.getBorderBoxHeight(c))); for (int i = 0; i < store.breaks.size(); i++) { ColumnBreakOpportunity br = store.breaks.get(i); @@ -223,7 +222,7 @@ private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidt // We need the max height of the column which is the bottom of the current box // minus the top of the column. - finalHeight = Math.max((yProposedFinal + ch.getHeight()) - current.pasteY, finalHeight); + finalHeight = Math.max((yProposedFinal + ch.getBorderBoxHeight(c)) - startY, finalHeight); // x position should be easy. int xAdjust = ((colIdx % columnCount) * colWidth) + ((colIdx % columnCount) * colGap); @@ -246,10 +245,12 @@ private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidt if (nextBr != null) { Box next = nextBr.box; - int nextYHeight = next.getAbsY() + yAdjust + next.getHeight() - current.pasteY; + int nextYHeight = next.getAbsY() + yAdjust + next.getBorderBoxHeight(c) - current.pasteY; - if (nextYHeight > current.maxColHeight) { - // We have moved past the bottom of the current column. + if (nextYHeight > current.maxColHeight || + ch.getStyle().isColumnBreakAfter() || + next.getStyle().isColumnBreakBefore()) { + // We have moved past the bottom of the current column (or explicit break). // Time for a new column. // FIXME: What if box doesn't fit in new column either? int newColIdx = colIdx + 1; @@ -279,7 +280,7 @@ private int adjustUnbalanced(LayoutContext c, Box child, int colGap, int colWidt if (haveFloats) { layoutFloats(columnMap, this.getPersistentBFC(), columnCount, colWidth, colGap); } - + return finalHeight; } @@ -315,7 +316,6 @@ public void layout(LayoutContext c, int contentStart) { int height = adjustUnbalanced(c, _child, (int) colGap, colWidth, colCount, this.getLeftMBP() + this.getX()); _child.setHeight(0); this.setHeight(height); - c.popBFC(); } diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-floats-unbalanced.pdf b/openhtmltopdf-examples/src/main/resources/visualtest/expected/text/columns-floats-unbalanced.pdf new file mode 100644 index 0000000000000000000000000000000000000000..54bc6f095d8c39b6c4e742dc8e5c86edf6e74993 GIT binary patch literal 40536 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 G1N{pTqfSHs literal 0 HcmV?d00001 diff --git a/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html index b4221243b..2709441d9 100644 --- a/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html +++ b/openhtmltopdf-examples/src/main/resources/visualtest/html/text/columns-floats-unbalanced.html @@ -15,18 +15,30 @@ column-count: 3; column-fill: auto; /* As opposed to balance */ } +div.cols h2 { + margin-top: 4px; + border-top: 5px solid orange; + font-weight: normal; +} +h1 { + margin: 0; + padding: 2px; + font-size: 20px; + font-weight: normal; + text-align: center; +} -
    +

    Column Support

    +

    Chapter 1

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse volutpat feugiat massa vitae congue. - -
    -

    Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sed non blandit nisl, id scelerisque sem. -

    +
    +

    Chapter 2

    +
    1. One
    2. Two
    3. @@ -35,18 +47,23 @@ Pellentesque sagittis maximus elit et eleifend. Mauris vitae eros quis eros bibendum bibendum. Cras sed massa lectus. Suspendisse mollis mi id tortor commodo, in semper eros gravida. Integer pulvinar nulla urna, vel sagittis leo maximus id. Nam sit amet arcu non justo molestie interdum. Phasellus gravida nisi ut bibendum feugiat. -
      -In vitae nulla nec sapien imperdiet interdum eget et sem. Aenean ultricies augue vitae quam viverra, et -maximus tortor eleifend. +
      +

      Chapter 3

      +
      +This is a left float.
      +Aenean ultricies augue vitae quam viverra, et maximus tortor eleifend. Duis quis venenatis turpis, eu sollicitudin magna. Proin ullamcorper sem vitae sem facilisis luctus. Mauris sed lectus ac nulla placerat pellentesque ut vel diam. Sed vulputate ornare sem vel placerat. -
      -
      +
      +
      Nam auctor libero ante, et volutpat arcu dictum ac. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nullam non sodales augue, in efficitur turpis. Duis maximus a orci in consequat. +
      +

      Chapter 4

      +Flyingsaucer Mauris luctus nec ex et volutpat. Nullam mollis luctus ultrices. Sed malesuada porttitor dui et blandit. Fusce ultrices metus sem, sed vehicula sem hendrerit vel. Phasellus sed turpis eget urna luctus sagittis in eget mauris. Vivamus rhoncus cursus lectus vel commodo. Nunc vulputate commodo porta. Nunc eget augue nec nisl hendrerit tincidunt sit diff --git a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java index c090438a4..7adb5b889 100644 --- a/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java +++ b/openhtmltopdf-examples/src/test/java/com/openhtmltopdf/visualregressiontests/TextVisualRegressionTest.java @@ -633,9 +633,9 @@ public void testColumnsNestedUnbalanced() throws IOException { /** * Tests columns containing floated and clear elements. + * Also tests explicit column breaks. */ @Test - @Ignore // Basics done but needs more work about breaking on appropriate locations in floats. public void testColumnsFloatsUnbalanced() throws IOException { assertTrue(run("columns-floats-unbalanced")); }