From 7c4b528b3c11702746a3076217c7f6c7493d923f Mon Sep 17 00:00:00 2001 From: Dmitry Chubrick Date: Thu, 27 Jun 2024 10:28:43 +0000 Subject: [PATCH] Replace grid track sizing algorithm with ported code from chromium * Also implement 5th point from grid track sizing algroithm DEVSIX-8387 Autoported commit. Original commit hash: [56f1ad83d] --- .../cmp_autoRepeatTestWithRowGapTest.pdf | Bin 1269 -> 1279 bytes .../cmp_columnFlowWithBigCellsTest.pdf | Bin 1278 -> 1282 bytes .../GridContainerTest/cmp_frColumnsTest.pdf | Bin 1496 -> 1496 bytes .../LayoutExceptionMessageConstant.cs | 4 +- .../itext/layout/renderer/GridSizer.cs | 2 +- .../layout/renderer/GridTemplateResolver.cs | 10 +- .../itext/layout/renderer/GridTrackSizer.cs | 1002 ++++++++++++----- port-hash | 2 +- 8 files changed, 754 insertions(+), 266 deletions(-) diff --git a/itext.tests/itext.layout.tests/resources/itext/layout/GridContainerTest/cmp_autoRepeatTestWithRowGapTest.pdf b/itext.tests/itext.layout.tests/resources/itext/layout/GridContainerTest/cmp_autoRepeatTestWithRowGapTest.pdf index a04397bfd354b6c32b6b5f8c03566c1b2ebf818d..3452d047f4602eea24f387b214b2a47481e72a15 100644 GIT binary patch delta 623 zcmey$`JZ!wNxhMQiJe_>Nl|KIE?32z-b=grnhYe`9{%0m)wk=4+ynP8VF}Y?aZRj+ z4Hq=3eip5mve5Og%cp0a`?u&C9Qu8Jio!&dJ^zyhT_e_f~ zd5G7dbsH?NR;_+FVWr_5&AuMLs56JAtevwguGe3<=-Py3iRa5AxzA_3|ES`f@#p#t z&61>hHog8mOBVcjo)E3OW7TIlgEZsqyKa>6?Fq?$pP-_#FT3z*mZ0oX>)3Yv@(;h( zI-UtTcY4*QZyfROU+^Y3FM2ztVvX4P_srfQGtwtBF>dEDGBY$WFflcme2`HR&e{Bk z(Sgy{(on$w1QhZVxWEhp19Kx23^5Z!OAIk1i^=iK>h;EE2$_7gVZ#mL?BKxN=Y;}FiJ5`F*HjuGBr$1 zPBgSIGEGUfOfyb3O0uv>HZig=F;25cGqE%=G)ze|1hOqsO$<_#%uUTw&6DcWOfAff zOihgwjV+SQlPwGk4AT-5Q_PHvlM@qD?P%@Nl|KIE?32z-b;@BO$s6hKK}kKBJcg&a`M`v?i{^* zR*{kN_C`F<73QjM|6f;~-!I1D^(I{_K_*xDw|mf;i3?6>Xgp-meWRMgB^{`8dfzS1 zGZK@;BV@C;PBTA|D*taqi}0VTMncPDo|^v5ags{)HVN-FS?u3**wCn;c=e>t2oru` zq2O7GR@MtvyUlsm#Jns?P+jd#4DMMqw4(EeW>cFuOGRp@d4-yh#(1fB`Kce=E6 z?{R6GOKp{_o z3(PPuFgKj+$Sf>sV2L4PWIj2QS)I{n@!@L=$66qcoGml$7LDOG7gYbIX*pi zThlME%dEQO<%MUbr>~!Wt-9zuc~a0Q)yGz*Y3wy)Py4rahwQ|4 z8!V@nWy?*NA@^r?$`O%;9RV$Nf#)qISBA$4uQSk0&w7(HWm1&$6c<^6pQX0X4_$h7 z^~}o(0g+FdaT>2Bik2<<+WzpA!S{1(ew-;YbWEOoh4GVVXF)UB|^n0Ia^o+2{+>G0_%#6$o%?%BV%(V@S)eQ{P zHT8Y-Q(O{DQZ-zxj0}uS4B!ejzh-n~v^7yM00D(O1uihdz`)$d97D{+$OuEs$ii}R zCbK%D<>ZOXQuQXLhL$Nw29_44MrKCF$(AXGmTBfGmdVBjKwFK|%q)@(O-+;1Oij&` zlZ}%TEiDq0EzQ%6l2gpnQVo;S%q>!r&5e?i%uS4pEfdpH%?*-LO_P(1Q_PYKQ&P;5 zO^pl<%z=7R4Aaa^%?vEkEKGsg(+rc$OpVP<6I1Ff>}&?o2`oT2SWI5a;>lsbrK;-c H@5TiHXJ*x; delta 607 zcmZqT`o}rJtlrqpuDGNqH8Gc~VovX*-F$}>L>#{V7VX&;BB%Qz>r$Uou$^O6VMg$x ztLLX*3bAr<=3qW~^5jl)o(92<$G04?5W2CiWA6;5#S0pxc?zZ#tk7|um?KkSliH(L z*?xCxaP`lYAgd4gO07#R+*sWzeV$0Q2H?cHI yOfol1N-{Q0HA*r{2GWK}mX=0o#wn?Gw0D9$&<*&UFnJ@3Cx;1_s;aBM8y5gw8p`ni diff --git a/itext.tests/itext.layout.tests/resources/itext/layout/GridContainerTest/cmp_frColumnsTest.pdf b/itext.tests/itext.layout.tests/resources/itext/layout/GridContainerTest/cmp_frColumnsTest.pdf index 10e760c2c2c3311370dfe6e41d72654f09dc434c..4f4d9520c7adbb2dcd675652e99cfea98c6c781e 100644 GIT binary patch delta 742 zcmcb?eS>>~YrWr510L7!HLh+EPLoZ1Hz^(LyW-!_-tyu@!+-I&-pR-Nn7u<+zuoz5 zuiyHtOOv<#dL8?7#pRT*%W^u@+M+-16kjQlr~fA?|IyDMi{h?`N}gSQp!4MHOvezP zzALRBE~&3LZ}F8jywfdA2)@iFA!aDe*P?9pAZ&w!T=>@yIa^EXtM*PgezIcy_3+Z| z`)dE+`2E${c5O}1of(tAC9PC@zEWh}cGdn|N0tv4-ZZ)BcerTl1s|Nbxw)__ro;47 z>P*jRDGp{<{>I#ylN26^3bhFzJ|T9NTXC*z%Y@Z7hCKJ*sjZS1=h;#|VL|zkMN=9a z!@KSo__FyY@8--{+`K{X=b`!tMfa@o+SVIyRd@F*3uW^^Z*wt=ne)-(aP5)rY~80e znn}NwUgNuEUBrJK%KomO!1Au2EIu3%2X0`08v=r_`|hPJdbCQnY+_ zg4KyPXL1A=?SB2A!+iG5(;fDH3Qx+TAK#cK=h3C^zHf@Z(84h0{41|?CMrA>~Ykl4k1)jF@c}*X8Eiv@t+Z2}d-K(lVEA^@5w4nT z|L+%=E|%53@t(i=UYp>XG=(3DHusy?+j1BdR5yHic=>&IrRnuUE4B$8XSa!K`E+oj zg-~SB+*uB{C(d1PgK6~)afJ=6o2RyIcs13b^bkj7)Y-?}5d~#?>yO+|blCEGw&mLQ z_U084_Gdq9-Hzz8;QOYaUYgg_n4KEAYf}ivqrjP*p1K=EbawiwrUtRbiqCdgG~w9k z*2i5ZuI`qWN_ub=;A2H6e1oUTp4z|4XC3fv=o>fQQ z=^xrJXYw}-9m|`i zX0RFUOl)G>IQRK~1#@0uuM6_(6aMV(-?-kN-%@pMvh~-=M^;20ur}2`9kk(zl(K&H zwBql}e3o$Bb$qa3ZRV=ns6Jzj;$`#v?G%h>e&lwxT^Ks~DdToc17kx2BV$ubZ3AO< z0|RwUec${Pm&B4(4HqjT10xdyGnj(SN0|;V)~6+<7$lh)SQuFv8z-k2r=%DfrKFjg zn_C(unV2RerWvFqB^#Jq0>w#QxYwWEYplEQp^&QjEvKg42+G;%nZ{E z3``OY%#%$GQjC)fQw&lPEeuUeEfZ7HQh_eAFgF8=nj4v?Bqf=eSQsQ3TN)XgnbaHF R*$ku;Sb%OYoZQYD2mpZ8ABg|} diff --git a/itext/itext.layout/itext/layout/exceptions/LayoutExceptionMessageConstant.cs b/itext/itext.layout/itext/layout/exceptions/LayoutExceptionMessageConstant.cs index 1bce86f136..0085777eb1 100644 --- a/itext/itext.layout/itext/layout/exceptions/LayoutExceptionMessageConstant.cs +++ b/itext/itext.layout/itext/layout/exceptions/LayoutExceptionMessageConstant.cs @@ -67,7 +67,9 @@ public sealed class LayoutExceptionMessageConstant { public const String GRID_AUTO_REPEAT_CAN_BE_USED_ONLY_ONCE = "Automatic repetitions in the grid template are allowed only once per template."; - public const String GRID_AUTO_REPEAT_CANNOT_BE_COMBINED_WITH_INDEFINITE_SIZES = "Automatic repetitions in the grid template cannot be combined with intrinsic or flexible sizes."; + public const String GRID_AUTO_REPEAT_CANNOT_BE_COMBINED_WITH_INDEFINITE_SIZES = "Automatic repetitions in the grid template cannot be combined with intrinsic or flexible sizes."; + + public const String FLEXIBLE_ARENT_ALLOWED_AS_MINIMUM_IN_MINMAX = "Flexible values aren't allowed as minimum in minmax grid function."; private LayoutExceptionMessageConstant() { } diff --git a/itext/itext.layout/itext/layout/renderer/GridSizer.cs b/itext/itext.layout/itext/layout/renderer/GridSizer.cs index 3fb8356322..eb239b5104 100644 --- a/itext/itext.layout/itext/layout/renderer/GridSizer.cs +++ b/itext/itext.layout/itext/layout/renderer/GridSizer.cs @@ -177,7 +177,7 @@ private void ResolveGridColumns() { colsValues.Add(columnAutoWidth); } else { - colsValues.Add(new FlexValue(1f)); + colsValues.Add(AutoValue.VALUE); } } } diff --git a/itext/itext.layout/itext/layout/renderer/GridTemplateResolver.cs b/itext/itext.layout/itext/layout/renderer/GridTemplateResolver.cs index 703dc56076..77b5cff431 100644 --- a/itext/itext.layout/itext/layout/renderer/GridTemplateResolver.cs +++ b/itext/itext.layout/itext/layout/renderer/GridTemplateResolver.cs @@ -141,9 +141,15 @@ private float ProcessValue(TemplateValue value) { // or as its minimum track sizing function otherwise // if encountered intrinsic or flexible before, then it doesn't matter what to process bool currentValue = containsIntrinsicOrFlexible; - float length = ProcessValue(((MinMaxValue)value).GetMax()); + MinMaxValue minMaxValue = (MinMaxValue)value; + if (minMaxValue.GetMin().GetType() == TemplateValue.ValueType.FLEX) { + // A future level of CSS Grid spec may allow minimums, but not now + throw new InvalidOperationException(LayoutExceptionMessageConstant.FLEXIBLE_ARENT_ALLOWED_AS_MINIMUM_IN_MINMAX + ); + } + float length = ProcessValue(minMaxValue.GetMax()); if (containsIntrinsicOrFlexible) { - length = ProcessValue(((MinMaxValue)value).GetMin()); + length = ProcessValue(minMaxValue.GetMin()); } containsIntrinsicOrFlexible = currentValue; result.SetFreeze(false); diff --git a/itext/itext.layout/itext/layout/renderer/GridTrackSizer.cs b/itext/itext.layout/itext/layout/renderer/GridTrackSizer.cs index a079f6b257..8d9ddc1b63 100644 --- a/itext/itext.layout/itext/layout/renderer/GridTrackSizer.cs +++ b/itext/itext.layout/itext/layout/renderer/GridTrackSizer.cs @@ -1,24 +1,31 @@ /* -This file is part of the iText (R) project. -Copyright (c) 1998-2024 Apryse Group NV -Authors: Apryse Software. - -This program is offered under a commercial and under the AGPL license. -For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below. - -AGPL licensing: -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU Affero General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Affero General Public License for more details. - -You should have received a copy of the GNU Affero General Public License -along with this program. If not, see . +Copyright 2015 The Chromium Authors + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. +* Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using System; using System.Collections.Generic; @@ -30,7 +37,9 @@ You should have received a copy of the GNU Affero General Public License namespace iText.Layout.Renderer { //\cond DO_NOT_DOCUMENT - // 12.3. Track Sizing Algorithm + // 12.3. Track Sizing Algorithm https://drafts.csswg.org/css-grid-2/#algo-track-sizing + // More than half of the code in that class was ported from chromium code on C++ + // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/layout/grid/grid_layout_algorithm.cc;l=1858 /// Class representing a track sizing algorithm. internal class GridTrackSizer { private readonly Grid grid; @@ -54,6 +63,7 @@ internal class GridTrackSizer { /// grid order internal GridTrackSizer(Grid grid, IList values, float gap, float availableSpace, Grid.GridOrder order) { + // iText's method this.grid = grid; this.availableSpace = availableSpace; this.gap = gap; @@ -62,8 +72,11 @@ internal GridTrackSizer(Grid grid, IList values, float gap, float ava GridTrackSizer.Track track = new GridTrackSizer.Track(); track.value = value; tracks.Add(track); + if (track.value.GetType() == TemplateValue.ValueType.FLEX) { + track.value = new MinMaxValue(AutoValue.VALUE, (FlexValue)track.value); + } } - if (availableSpace < 0) { + if (JavaUtil.FloatCompare(availableSpace, -1f) == 0) { for (int i = 0; i < tracks.Count; ++i) { GridTrackSizer.Track track = tracks[i]; if (track.value.GetType() == TemplateValue.ValueType.PERCENT) { @@ -90,6 +103,7 @@ internal GridTrackSizer(Grid grid, IList values, float gap, float ava /// list of points, representing track sizes with expanded percentages in case of inline calculation. /// internal virtual GridTrackSizer.TrackSizingResult SizeTracks() { + // iText's method // First step (12.4. Initialize Track Sizes) InitializeTrackSizes(); // Second step (12.5. Resolve Intrinsic Track Sizes) @@ -99,99 +113,126 @@ internal virtual GridTrackSizer.TrackSizingResult SizeTracks() { // Fourth step (12.7. Expand Flexible Tracks) ExpandFlexibleTracks(); // Fifth step (12.8. Stretch auto Tracks) - // Skip for now + StretchAutoTracks(); return new GridTrackSizer.TrackSizingResult(tracks, gap, percentValueIndexes); } //\endcond - private void MaximizeTracks() { - float freeSpace = GetFreeSpace(); - if (availableSpace > 0) { - float leftSpace = (float)freeSpace; - while (leftSpace > 0.0f) { - int unfrozenTracks = 0; - foreach (GridTrackSizer.Track track in tracks) { - if (JavaUtil.FloatCompare(track.baseSize, track.growthLimit) < 0) { - unfrozenTracks++; - } - } - if (unfrozenTracks == 0) { - break; - } - float diff = leftSpace / unfrozenTracks; - foreach (GridTrackSizer.Track track in tracks) { - if (JavaUtil.FloatCompare(track.baseSize, track.growthLimit) < 0) { - float trackDiff = Math.Min(track.growthLimit, track.baseSize + diff) - track.baseSize; - track.baseSize += trackDiff; - leftSpace -= trackDiff; - } - } + // chromium's method + private void StretchAutoTracks() { + // iText's comment: for now consider that we always have content-distribution property equals to `normal` + // Expand tracks that have an 'auto' max track sizing function by dividing any + // remaining positive, definite free space equally amongst them. + IList tracksToGrow = new List(); + foreach (GridTrackSizer.Track track in tracks) { + if (track.HasAutoMax()) { + tracksToGrow.Add(track); } } - else { - foreach (GridTrackSizer.Track track in tracks) { - if (JavaUtil.FloatCompare(track.baseSize, track.growthLimit) < 0) { - track.baseSize = track.growthLimit; - } - } + if (tracksToGrow.IsEmpty()) { + return; + } + float freeSpace = DetermineFreeSpace(); + // iText's comment: the case when grid container has min-width\height is processed in the GridContainerRenderer + if (JavaUtil.FloatCompare(freeSpace, -1f) == 0) { + return; + } + DistributeExtraSpaceToTracks(freeSpace, 0, GridTrackSizer.GridItemContributionType.FOR_FREE_SPACE, tracksToGrow + , tracksToGrow, true); + foreach (GridTrackSizer.Track track in tracksToGrow) { + track.baseSize += track.incurredIncrease; + track.EnsureGrowthLimitIsNotLessThanBaseSize(); + } + } + + // chromium's method + private void MaximizeTracks() { + float freeSpace = DetermineFreeSpace(); + if (JavaUtil.FloatCompare(freeSpace, 0f) == 0) { + return; + } + IList tracksToGrow = new List(tracks); + DistributeExtraSpaceToTracks(freeSpace, 0f, GridTrackSizer.GridItemContributionType.FOR_FREE_SPACE, tracksToGrow + , null, true); + foreach (GridTrackSizer.Track track in tracksToGrow) { + track.baseSize += track.incurredIncrease; + track.EnsureGrowthLimitIsNotLessThanBaseSize(); } } + // chromium's method private void ExpandFlexibleTracks() { bool thereIsFlexibleTrack = false; foreach (GridTrackSizer.Track track in tracks) { - if (track.value.GetType() == TemplateValue.ValueType.FLEX) { + if (track.IsFlexibleTrack()) { thereIsFlexibleTrack = true; break; } } + // iText's comment: this check is performed in GridTrackSizer.sizeTracks method in chromium if (!thereIsFlexibleTrack) { return; } + float freeSpace = DetermineFreeSpace(); + // If the free space is zero or if sizing the grid container under a + // min-content constraint, the used flex fraction is zero. + if (JavaUtil.FloatCompare(freeSpace, 0f) == 0) { + return; + } float frSize = 0; - if (availableSpace > 0.0f) { - // If the free space is zero or if sizing the grid container under a min-content constraint: - float freeSpace = (float)GetFreeSpace(); - if (freeSpace < 0.0f) { - return; - } - // Otherwise, if the free space is a definite length: - frSize = FindFrSize(tracks, GetAvailableSpaceForSizing()); + if (JavaUtil.FloatCompare(freeSpace, -1f) != 0) { + // Otherwise, if the free space is a definite length, the used flex fraction + // is the result of finding the size of an fr using all of the grid tracks + // and a space to fill of the available grid space. + frSize = FindFrSize(tracks, availableSpace); } else { - // Otherwise, if the free space is an indefinite length: - foreach (GridTrackSizer.Track track in tracks) { - if (track.value.GetType() == TemplateValue.ValueType.FLEX) { - frSize = Math.Max(frSize, track.baseSize / ((FlexValue)track.value).GetFlex()); - } - } + // Otherwise, if the free space is an indefinite length, the used flex + // fraction is the maximum of: + // - For each grid item that crosses a flexible track, the result of + // finding the size of an fr using all the grid tracks that the item + // crosses and a space to fill of the item's max-content contribution. foreach (GridCell cell in grid.GetUniqueGridCells(order)) { - bool atLeastOneFlexTrack = false; - IList affectedTracks = GetAffectedTracks(cell); - foreach (GridTrackSizer.Track track in affectedTracks) { - if (track.value.GetType() == TemplateValue.ValueType.FLEX) { - atLeastOneFlexTrack = true; - break; + IList flexSpannedTracks = new List(); + IList spannedTracks = GetSpannedTracks(cell); + foreach (GridTrackSizer.Track track in spannedTracks) { + if (track.IsFlexibleTrack()) { + flexSpannedTracks.Add(track); } } - if (!atLeastOneFlexTrack) { + // iText's comment: grid_item.IsConsideredForSizing(track_direction) check was skipped, because it isn't clear how it works + if (flexSpannedTracks.IsEmpty()) { continue; } - float maxContribution = CalculateMinMaxContribution(cell, false); - frSize = Math.Max(frSize, FindFrSize(affectedTracks, maxContribution)); + float gridItemFrSize = FindFrSize(spannedTracks, CalculateMinMaxContribution(cell, false)); + frSize = Math.Max(gridItemFrSize, frSize); + } + // - For each flexible track, if the flexible track's flex factor is + // greater than one, the result of dividing the track's base size by its + // flex factor; otherwise, the track's base size. + foreach (GridTrackSizer.Track track in tracks) { + if (!track.IsFlexibleTrack()) { + continue; + } + float trackFlexFactor = Math.Max(track.GetFlexFactor(), 1); + frSize = Math.Max(track.baseSize / trackFlexFactor, frSize); } } + // iText's comment: logic with leftover_size and expanded_size skipped because it isn't needed for java foreach (GridTrackSizer.Track track in tracks) { - if (track.value.GetType() == TemplateValue.ValueType.FLEX) { - float newBaseSize = frSize * ((FlexValue)track.value).GetFlex(); - if (newBaseSize > track.baseSize) { - track.baseSize = newBaseSize; - } + if (!track.IsFlexibleTrack()) { + continue; + } + float frShare = frSize * track.GetFlexFactor(); + if (frShare >= track.baseSize) { + track.baseSize = frShare; + track.EnsureGrowthLimitIsNotLessThanBaseSize(); } } } - private IList GetAffectedTracks(GridCell cell) { + // iText's method + private IList GetSpannedTracks(GridCell cell) { IList affectedTracks = new List(); for (int i = cell.GetStart(order); i < cell.GetEnd(order); i++) { affectedTracks.Add(tracks[i]); @@ -199,44 +240,50 @@ private void ExpandFlexibleTracks() { return affectedTracks; } - private float GetAvailableSpaceForSizing() { - // Grid sizing algorithm says "Gutters are treated as empty fixed-size tracks for the purpose of the algorithm." - // But relative gaps haven't supported yet, it is why to make algorithm simpler, available space just reduced by gaps. - return availableSpace - ((tracks.Count - 1) * gap); - } - - private float GetFreeSpace() { - float freeSpace = GetAvailableSpaceForSizing(); - foreach (GridTrackSizer.Track track in tracks) { - freeSpace -= track.baseSize; + // chromium's method + private float DetermineFreeSpace() { + // iText's comment: method was simplified, because we don't support different sizing constraint + float freeSpace = availableSpace; + if (JavaUtil.FloatCompare(freeSpace, -1f) != 0) { + foreach (GridTrackSizer.Track track in tracks) { + freeSpace -= track.baseSize; + } + freeSpace -= (tracks.Count - 1) * gap; + // If tracks consume more space than the grid container has available, + // clamp the free space to zero as there's no more room left to grow. + return Math.Max(freeSpace, 0); } - return freeSpace; + return -1; } - private static float FindFrSize(IList affectedTracks, float spaceToFill) { + // iText's method + private float FindFrSize(IList affectedTracks, float leftoverSpace) { + // iText's comment: initially was implemented method from chromium but it worked worse in some cases than our implementation // 12.7.1. Find the Size of an 'fr' float frSize = 0; bool allFlexTracksSatisfied = false; bool[] ignoreTracks = new bool[affectedTracks.Count]; while (!allFlexTracksSatisfied) { - float leftoverSpace = spaceToFill; + float currentLeftoverSpace = leftoverSpace; + int totalTrackCount = 0; float flexFactorSum = 0; for (int i = 0; i < affectedTracks.Count; i++) { GridTrackSizer.Track track = affectedTracks[i]; - if (track.value.GetType() == TemplateValue.ValueType.FLEX && !ignoreTracks[i]) { - flexFactorSum += ((FlexValue)track.value).GetFlex(); + totalTrackCount++; + if (track.IsFlexibleTrack() && !ignoreTracks[i]) { + flexFactorSum += track.GetFlexFactor(); } else { - leftoverSpace -= track.baseSize; + currentLeftoverSpace -= track.baseSize; } } + currentLeftoverSpace -= (totalTrackCount - 1) * gap; flexFactorSum = flexFactorSum < 1 ? 1 : flexFactorSum; - float hyphFrSize = leftoverSpace / flexFactorSum; + float hyphFrSize = currentLeftoverSpace / flexFactorSum; allFlexTracksSatisfied = true; for (int i = 0; i < affectedTracks.Count; i++) { GridTrackSizer.Track track = affectedTracks[i]; - if (track.value.GetType() == TemplateValue.ValueType.FLEX && !ignoreTracks[i] && hyphFrSize * ((FlexValue) - track.value).GetFlex() < track.baseSize) { + if (track.IsFlexibleTrack() && !ignoreTracks[i] && hyphFrSize * track.GetFlexFactor() < track.baseSize) { ignoreTracks[i] = true; allFlexTracksSatisfied = false; } @@ -248,193 +295,598 @@ private static float FindFrSize(IList affectedTracks, floa return frSize; } + // chromium's method private void ResolveIntrinsicTrackSizes() { - // 1. Shim baseline-aligned items so their intrinsic size contributions reflect their baseline alignment. - // Not sure whether we need to do anything in first point - // 2. Size tracks to fit non-spanning items. - for (int i = 0; i < tracks.Count; i++) { - GridTrackSizer.Track track = tracks[i]; - GridValue minTrackSizingValue = track.value; - GridValue maxTrackSizingValue = track.value; - if (track.value.GetType() == TemplateValue.ValueType.MINMAX) { - minTrackSizingValue = ((MinMaxValue)track.value).GetMin(); - maxTrackSizingValue = ((MinMaxValue)track.value).GetMax(); - } - ICollection cells = grid.GetUniqueCellsInTrack(order, i); - // -> For max-content minimums: - if (minTrackSizingValue.GetType() == TemplateValue.ValueType.MAX_CONTENT) { - float maxContribution = 0; - foreach (GridCell cell in cells) { - // non-spanning items only - if (cell.GetGridSpan(order) == 1) { - float contribution = CalculateMinMaxContribution(cell, false); - maxContribution = Math.Max(maxContribution, contribution); - } - } - track.baseSize = maxContribution; - } - // -> For min-content minimums: - // -> For auto minimums: (also the case if track specified by fr value) - if (minTrackSizingValue.GetType() == TemplateValue.ValueType.AUTO || minTrackSizingValue.GetType() == TemplateValue.ValueType - .FLEX || minTrackSizingValue.GetType() == TemplateValue.ValueType.MIN_CONTENT || minTrackSizingValue.GetType - () == TemplateValue.ValueType.FIT_CONTENT) { - float maxContribution = 0; - foreach (GridCell cell in cells) { - // non-spanning items only - if (cell.GetGridSpan(order) == 1) { - float contribution = CalculateMinMaxContribution(cell, true); - maxContribution = Math.Max(maxContribution, contribution); - } - } - track.baseSize = maxContribution; - } - // -> For min-content maximums: - if (maxTrackSizingValue.GetType() == TemplateValue.ValueType.MIN_CONTENT && track.baseSize > 0.0f) { - track.growthLimit = track.baseSize; - } - // -> For max-content maximums: - // Treat auto as max-content for max track sizing function - if (maxTrackSizingValue.GetType() == TemplateValue.ValueType.AUTO || maxTrackSizingValue.GetType() == TemplateValue.ValueType - .MAX_CONTENT || maxTrackSizingValue.GetType() == TemplateValue.ValueType.FIT_CONTENT) { - float maxContribution = 0; - foreach (GridCell cell in cells) { - // non-spanning items only - if (cell.GetGridSpan(order) == 1) { - float contribution = CalculateMinMaxContribution(cell, false); - maxContribution = Math.Max(maxContribution, contribution); - } - } - if (maxContribution > 0.0f) { - track.growthLimit = maxContribution; - if (maxTrackSizingValue.GetType() == TemplateValue.ValueType.FIT_CONTENT) { - //For fit-content() maximums, furthermore clamp this growth limit by the fit-content() argument. - float maxSize = ((FitContentValue)maxTrackSizingValue).GetMaxSizeForSpace(availableSpace); - track.growthLimit = Math.Min(track.growthLimit, maxSize); - } - } - } - // if a track’s growth limit is now less than its base size - if (track.growthLimit > 0.0f && track.baseSize > 0.0f && track.baseSize > track.growthLimit) { - track.growthLimit = track.baseSize; - } - } - // 3. Increase sizes to accommodate spanning items crossing content-sized tracks. + // iText's comment: part for subgrid is skipped + // itext's comment: reordering grid items is skipped, grid items groups are created through `for` cycle + // iText's comment: 2 - Size tracks to fit non-spanning items + // iText's comment: 3 - Increase sizes to accommodate spanning items crossing content-sized tracks int maxSpanCell = 0; foreach (GridCell cell in grid.GetUniqueGridCells(order)) { maxSpanCell = Math.Max(maxSpanCell, cell.GetGridSpan(order)); } - for (int span = 2; span <= maxSpanCell; span++) { + // First, process the items that don't span a flexible track. + for (int span = 1; span <= maxSpanCell; span++) { + IList group = new List(); foreach (GridCell cell in grid.GetUniqueGridCells(order)) { + // Each iteration considers all items with the same span size. if (cell.GetGridSpan(order) == span) { bool flexTracksExist = false; - IList affectedTracks = GetAffectedTracks(cell); - foreach (GridTrackSizer.Track track in affectedTracks) { - if (track.value.GetType() == TemplateValue.ValueType.FLEX) { + IList spannedTracks = GetSpannedTracks(cell); + foreach (GridTrackSizer.Track track in spannedTracks) { + if (track.IsFlexibleTrack()) { flexTracksExist = true; + break; } } if (flexTracksExist) { continue; } - float contribution = CalculateMinMaxContribution(cell, true); - // 3.1 For intrinsic minimums: - // 3.2 For content-based minimums: - // 3.3 For max-content minimums: - DistributeExtraSpace(affectedTracks, true, contribution); + group.Add(cell); } } + IncreaseTrackSizesToAccommodateGridItems(group, false, GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS + ); + IncreaseTrackSizesToAccommodateGridItems(group, false, GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS + ); + IncreaseTrackSizesToAccommodateGridItems(group, false, GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MINIMUMS + ); + IncreaseTrackSizesToAccommodateGridItems(group, false, GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MAXIMUMS + ); + IncreaseTrackSizesToAccommodateGridItems(group, false, GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MAXIMUMS + ); } - // 3.4 If at this point any track’s growth limit is now less than its base size: - // 3.5 For intrinsic maximums: - // 3.6 For max-content maximums: - // 4. Increase sizes to accommodate spanning items crossing flexible tracks: + // iText's comment: 4 - Increase sizes to accommodate spanning items crossing flexible tracks + // Now, process items spanning flexible tracks (if any). + IList group_1 = new List(); foreach (GridCell cell in grid.GetUniqueGridCells(order)) { - IList affectedTracks = new List(); - for (int i = cell.GetStart(order); i < cell.GetEnd(order); i++) { - if (tracks[i].value.GetType() == TemplateValue.ValueType.FLEX) { - affectedTracks.Add(tracks[i]); + IList flexSpannedTracks = new List(); + IList spannedTracks = GetSpannedTracks(cell); + foreach (GridTrackSizer.Track track in spannedTracks) { + if (track.IsFlexibleTrack()) { + flexSpannedTracks.Add(track); } } - if (affectedTracks.IsEmpty()) { + if (flexSpannedTracks.IsEmpty()) { continue; } - float contribution = CalculateMinMaxContribution(cell, true); - DistributeExtraSpaceWithFlexTracks(affectedTracks, contribution); + group_1.Add(cell); + } + if (!group_1.IsEmpty()) { + // We can safely skip contributions for maximums since a definition + // does not have an intrinsic max track sizing function. + IncreaseTrackSizesToAccommodateGridItems(group_1, true, GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS + ); + IncreaseTrackSizesToAccommodateGridItems(group_1, true, GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS + ); + IncreaseTrackSizesToAccommodateGridItems(group_1, true, GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MINIMUMS + ); } - // 5. If any track still has an infinite growth limit + // iText's comment: 5 - If any track still has an infinite growth limit + // iText's comment: in chromium this part in GridTrackSizer.sizeTracks method foreach (GridTrackSizer.Track track in tracks) { - if (track.growthLimit < 0) { + if (JavaUtil.FloatCompare(track.growthLimit, -1f) == 0) { track.growthLimit = track.baseSize; } } } - private void DistributeExtraSpaceWithFlexTracks(IList tracks, float sizeContribution - ) { - // 1. Find the space to distribute: - float trackSizes = 0.0f; - float sumFraction = 0.0f; + // chromium's method + private void IncreaseTrackSizesToAccommodateGridItems(IList group, bool isGroupSpanningFlexTrack + , GridTrackSizer.GridItemContributionType contributionType) { foreach (GridTrackSizer.Track track in tracks) { - trackSizes += track.baseSize; - sumFraction += ((FlexValue)track.value).GetFlex(); + track.plannedIncrease = -1; } - if (sumFraction < 1.0f) { - sumFraction = 1.0f; + foreach (GridCell cell in group) { + IList tracksToGrow = new List(); + IList tracksToGrowBeyondLimit = new List(); + float flexFactorSum = 0; + float spannedTrackSize = gap * (cell.GetGridSpan(order) - 1); + foreach (GridTrackSizer.Track track in GetSpannedTracks(cell)) { + spannedTrackSize += AffectedSizeForContribution(track, contributionType); + if (isGroupSpanningFlexTrack && !track.IsFlexibleTrack()) { + // From https://drafts.csswg.org/css-grid-2/#algo-spanning-flex-items: + // Distributing space only to flexible tracks (i.e. treating all other + // tracks as having a fixed sizing function). + continue; + } + if (IsContributionAppliedToTrack(track, contributionType)) { + if (JavaUtil.FloatCompare(track.plannedIncrease, -1f) == 0) { + track.plannedIncrease = 0; + } + if (isGroupSpanningFlexTrack) { + flexFactorSum += track.GetFlexFactor(); + } + tracksToGrow.Add(track); + if (ShouldUsedSizeGrowBeyondLimit(track, contributionType)) { + tracksToGrowBeyondLimit.Add(track); + } + } + } + if (tracksToGrow.IsEmpty()) { + continue; + } + // iText's comment: extraSpace calculation was simplified in comparison how it works in chromium, + // iText's comment: it is possible place for difference with browser + bool minTypeContribution = contributionType == GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS + || contributionType == GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS || contributionType + == GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MAXIMUMS; + // Subtract the corresponding size (base size or growth limit) of every + // spanned track from the grid item's size contribution to find the item's + // remaining size contribution. For infinite growth limits, substitute with + // the track's base size. This is the space to distribute, floor it at zero. + float extraSpace = CalculateMinMaxContribution(cell, minTypeContribution); + extraSpace = Math.Max(extraSpace - spannedTrackSize, 0); + if (JavaUtil.FloatCompare(extraSpace, 0) == 0) { + continue; + } + // From https://drafts.csswg.org/css-grid-2/#algo-spanning-flex-items: + // If the sum of the flexible sizing functions of all flexible tracks + // spanned by the item is greater than zero, distributing space to such + // tracks according to the ratios of their flexible sizing functions + // rather than distributing space equally. + if (!isGroupSpanningFlexTrack || JavaUtil.FloatCompare(flexFactorSum, 0f) == 0) { + DistributeExtraSpaceToTracks(extraSpace, 0, contributionType, tracksToGrow, tracksToGrowBeyondLimit.IsEmpty + () ? tracksToGrow : tracksToGrowBeyondLimit, true); + } + else { + // 'fr' units are only allowed as a maximum in track definitions, meaning + // that no track has an intrinsic max track sizing function that would allow + // it to grow beyond limits (see |ShouldUsedSizeGrowBeyondLimit|). + DistributeExtraSpaceToTracks(extraSpace, flexFactorSum, contributionType, tracksToGrow, null, false); + } + // For each affected track, if the track's item-incurred increase is larger + // than its planned increase, set the planned increase to that value. + foreach (GridTrackSizer.Track track in tracksToGrow) { + track.plannedIncrease = Math.Max(track.incurredIncrease, track.plannedIncrease); + } } - float space = Math.Max(0.0f, sizeContribution - trackSizes); - float spacePerFraction = space / sumFraction; - // 2. Distribute space up to limits: - // For flex values we know that they're can't be frozen so we can distribute all available space at once foreach (GridTrackSizer.Track track in tracks) { - DistributeSpaceToTrack(track, spacePerFraction * ((FlexValue)track.value).GetFlex()); + GrowAffectedSizeByPlannedIncrease(track, contributionType); } } - // 3. Distribute space to non-affected tracks: skipped - // 4. Distribute space beyond limits: skipped - private void DistributeExtraSpace(IList tracks, bool affectsBase, float sizeContribution + // chromium's method + private static void GrowAffectedSizeByPlannedIncrease(GridTrackSizer.Track track, GridTrackSizer.GridItemContributionType + contributionType) { + track.isInfinityGrowable = false; + float plannedIncrease = track.plannedIncrease; + // Only grow sets that accommodated a grid item. + if (JavaUtil.FloatCompare(plannedIncrease, -1f) == 0) { + return; + } + switch (contributionType) { + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MINIMUMS: { + track.baseSize += plannedIncrease; + track.EnsureGrowthLimitIsNotLessThanBaseSize(); + return; + } + + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MAXIMUMS: { + // Mark any tracks whose growth limit changed from infinite to finite in + // this step as infinitely growable for the next step. + track.isInfinityGrowable = JavaUtil.FloatCompare(track.growthLimit, -1f) == 0; + track.growthLimit = track.DefiniteGrowthLimit() + plannedIncrease; + return; + } + + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MAXIMUMS: { + track.growthLimit = track.DefiniteGrowthLimit() + plannedIncrease; + return; + } + } + } + + // FOR_FREE_SPACE not reachable here + // Follow the definitions from https://drafts.csswg.org/css-grid-2/#extra-space; + // notice that this method replaces the notion of "tracks" with "sets". + // chromium's method + private void DistributeExtraSpaceToTracks(float extraSpace, float flexFactorSum, GridTrackSizer.GridItemContributionType + contributionType, IList tracksToGrow, IList tracksToGrowBeyondLimits + , bool isEqualDistribution) { + if (JavaUtil.FloatCompare(extraSpace, -1f) == 0) { + // Infinite extra space should only happen when distributing free space at + // the maximize tracks step; in such case, we can simplify this method by + // "filling" every track base size up to their growth limit. + foreach (GridTrackSizer.Track track in tracksToGrow) { + track.incurredIncrease = GrowthPotentialForSet(track, contributionType, false); + } + return; + } + int growableTrackCount = 0; + foreach (GridTrackSizer.Track track in tracksToGrow) { + track.incurredIncrease = 0; + // From the first note in https://drafts.csswg.org/css-grid-2/#extra-space: + // If the affected size was a growth limit and the track is not marked + // "infinitely growable", then each item-incurred increase will be zero. + // + // When distributing space to growth limits, we need to increase each track + // up to its 'fit-content' limit. However, because of the note above, first + // we should only grow tracks marked as "infinitely growable" up to limits + // and then grow all affected tracks beyond limits. + // + // We can correctly resolve every scenario by doing a single sort of + // |sets_to_grow|, purposely ignoring the "infinitely growable" flag, then + // filtering out sets that won't take a share of the extra space at each + // step; for base sizes this is not required, but if there are no tracks + // with growth potential > 0, we can optimize by not sorting the sets. + if (JavaUtil.FloatCompare(GrowthPotentialForSet(track, contributionType, false), 0) != 0) { + growableTrackCount++; + } + } + float shareRatioSum = isEqualDistribution ? growableTrackCount : flexFactorSum; + // We will sort the tracks by growth potential in non-decreasing order to + // distribute space up to limits; notice that if we start distributing space + // equally among all tracks we will eventually reach the limit of a track or + // run out of space to distribute. If the former scenario happens, it should + // be easy to see that the group of tracks that will reach its limit first + // will be that with the least growth potential. Otherwise, if tracks in such + // group does not reach their limit, every upcoming track with greater growth + // potential must be able to increase its size by the same amount. + if (growableTrackCount != 0 || IsDistributionForGrowthLimits(contributionType)) { + // iText's comment: in chromium CompareTracksByGrowthPotential is lambda, + // iText's comment: but for porting purpose lambda was extracted to a class + // Only sort for equal distributions; since the growth potential of any + // flexible set is infinite, they don't require comparing. + if (JavaUtil.FloatCompare(flexFactorSum, 0) == 0) { + JavaCollectionsUtil.Sort(tracksToGrow, new GridTrackSizer.CompareTracksByGrowthPotential(this, contributionType + )); + } + } + // iText's comment: ExtraSpaceShare lambda was replaced with static method extraSpaceShare to resolve issue with working on java and porting + // Distribute space up to limits: + // - For base sizes, grow the base size up to the growth limit. + // - For growth limits, the only case where a growth limit should grow at + // this step is when its set has already been marked "infinitely growable". + // Increase the growth limit up to the 'fit-content' argument (if any); note + // that these arguments could prevent this step to fulfill the entirety of + // the extra space and further distribution would be needed. + foreach (GridTrackSizer.Track track in tracksToGrow) { + // Break early if there are no further tracks to grow. + if (growableTrackCount == 0) { + break; + } + // iText's comment: java doesn't allow change variables inside lambda, it is why ExtraSpaceShareFunctionParams was introduced + GridTrackSizer.ExtraSpaceShareFunctionParams changedParams = new GridTrackSizer.ExtraSpaceShareFunctionParams + (growableTrackCount, shareRatioSum, extraSpace); + track.incurredIncrease = ExtraSpaceShare(track, GrowthPotentialForSet(track, contributionType, false), isEqualDistribution + , changedParams); + growableTrackCount = changedParams.growableTrackCount; + shareRatioSum = changedParams.shareRatioSum; + extraSpace = changedParams.extraSpace; + } + // Distribute space beyond limits: + // - For base sizes, every affected track can grow indefinitely. + // - For growth limits, grow tracks up to their 'fit-content' argument. + if (tracksToGrowBeyondLimits != null && JavaUtil.FloatCompare(extraSpace, 0f) != 0) { + foreach (GridTrackSizer.Track track in tracksToGrowBeyondLimits) { + // iText's comment: BeyondLimitsGrowthPotential function was replaced by 1 line of code + // For growth limits, ignore the "infinitely growable" flag and grow all + // affected tracks up to their 'fit-content' argument (note that + // |GrowthPotentialForSet| already accounts for it). + float beyondLimitsGrowthPotential = IsDistributionForGrowthLimits(contributionType) ? GrowthPotentialForSet + (track, contributionType, true) : -1; + if (JavaUtil.FloatCompare(beyondLimitsGrowthPotential, 0) != 0) { + growableTrackCount++; + } + } + shareRatioSum = growableTrackCount; + foreach (GridTrackSizer.Track track in tracksToGrowBeyondLimits) { + // Break early if there are no further tracks to grow. + if (growableTrackCount == 0) { + break; + } + // iText's comment: BeyondLimitsGrowthPotential function was replaced by 1 line of code + // For growth limits, ignore the "infinitely growable" flag and grow all + // affected tracks up to their 'fit-content' argument (note that + // |GrowthPotentialForSet| already accounts for it). + float beyondLimitsGrowthPotential = IsDistributionForGrowthLimits(contributionType) ? GrowthPotentialForSet + (track, contributionType, true) : -1; + // iText's comment: java doesn't allow change variables inside lambda, it is why ExtraSpaceShareFunctionParams was introduced + GridTrackSizer.ExtraSpaceShareFunctionParams changedParams = new GridTrackSizer.ExtraSpaceShareFunctionParams + (growableTrackCount, shareRatioSum, extraSpace); + track.incurredIncrease = ExtraSpaceShare(track, beyondLimitsGrowthPotential, isEqualDistribution, changedParams + ); + growableTrackCount = changedParams.growableTrackCount; + shareRatioSum = changedParams.shareRatioSum; + extraSpace = changedParams.extraSpace; + } + } + } + + // chromium's method + private static float ExtraSpaceShare(GridTrackSizer.Track track, float growthPotential, bool isEqualDistribution + , GridTrackSizer.ExtraSpaceShareFunctionParams changedParams) { + // If this set won't take a share of the extra space, e.g. has zero growth + // potential, exit so that this set is filtered out of |share_ratio_sum|. + if (JavaUtil.FloatCompare(growthPotential, 0.0f) == 0) { + return 0; + } + int trackCount = 1; + float trackShareRatio = isEqualDistribution ? 1 : track.GetFlexFactor(); + // Since |share_ratio_sum| can be greater than the wtf_size_t limit, cap the + // value of |set_share_ratio| to prevent overflows. + if (trackShareRatio > changedParams.shareRatioSum) { + trackShareRatio = changedParams.shareRatioSum; + } + float extraSpaceShare; + if (JavaUtil.FloatCompare(trackShareRatio, changedParams.shareRatioSum) == 0) { + // If this set's share ratio and the remaining ratio sum are the same, it + // means that this set will receive all of the remaining space. Hence, we + // can optimize a little by directly using the extra space as this set's + // share and break early by decreasing the remaining growable track count + // to 0 (even if there are further growable tracks, since the share ratio + // sum will be reduced to 0, their space share will also be 0). + trackCount = changedParams.growableTrackCount; + extraSpaceShare = changedParams.extraSpace; + } + else { + extraSpaceShare = changedParams.extraSpace * trackShareRatio / changedParams.shareRatioSum; + } + if (JavaUtil.FloatCompare(growthPotential, -1f) != 0) { + extraSpaceShare = Math.Min(extraSpaceShare, growthPotential); + } + changedParams.growableTrackCount -= trackCount; + changedParams.shareRatioSum -= trackShareRatio; + changedParams.extraSpace -= extraSpaceShare; + return extraSpaceShare; + } + + // chromium class + // iText's comment: in chromium this class is lambda, but for porting purpose lambda was extracted to a class + private sealed class CompareTracksByGrowthPotential : IComparer { + private readonly GridTrackSizer gridTrackSizer; + + private readonly GridTrackSizer.GridItemContributionType contributionType; + + public CompareTracksByGrowthPotential(GridTrackSizer gridTrackSizer, GridTrackSizer.GridItemContributionType + contributionType) { + this.gridTrackSizer = gridTrackSizer; + this.contributionType = contributionType; + } + + public int Compare(GridTrackSizer.Track lhs, GridTrackSizer.Track rhs) { + float growthPotentialLhs = gridTrackSizer.GrowthPotentialForSet(lhs, contributionType, true); + float growthPotentialRhs = gridTrackSizer.GrowthPotentialForSet(rhs, contributionType, true); + if (JavaUtil.FloatCompare(growthPotentialLhs, -1f) == 0 || JavaUtil.FloatCompare(growthPotentialRhs, -1f) + == 0) { + // At this point we know that there is at least one set with + // infinite growth potential; if |a| has a definite value, then |b| + // must have infinite growth potential, and thus, |a| < |b|. + return JavaUtil.FloatCompare(growthPotentialLhs, -1f) == 0 ? 1 : -1; + } + // Straightforward comparison of definite growth potentials. + return JavaUtil.FloatCompare(growthPotentialLhs, growthPotentialRhs); + } + } + + // chromium's method + private static bool IsDistributionForGrowthLimits(GridTrackSizer.GridItemContributionType contributionType ) { - // 1. Find the space to distribute: - float trackSizes = 0; - int numberOfAffectedTracks = 0; - foreach (GridTrackSizer.Track track in tracks) { - GridValue value = track.value; - if (track.value.GetType() == TemplateValue.ValueType.MINMAX) { - value = affectsBase ? ((MinMaxValue)track.value).GetMin() : ((MinMaxValue)track.value).GetMax(); + switch (contributionType) { + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_FREE_SPACE: { + return false; + } + + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MAXIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MAXIMUMS: { + return true; } - trackSizes += affectsBase ? track.baseSize : track.growthLimit; - if (value.GetType() != TemplateValue.ValueType.POINT && value.GetType() != TemplateValue.ValueType.PERCENT - ) { - numberOfAffectedTracks++; + + default: { + return false; } } - float space = Math.Max(0, sizeContribution - trackSizes); - // 2. Distribute space up to limits: - while (space > 0.0f) { - float distributedSpace = space / numberOfAffectedTracks; - bool allFrozen = true; - foreach (GridTrackSizer.Track track in tracks) { - GridValue value = track.value; - if (track.value.GetType() == TemplateValue.ValueType.MINMAX) { - value = affectsBase ? ((MinMaxValue)track.value).GetMin() : ((MinMaxValue)track.value).GetMax(); + } + + // iText's class which is workaround to port c++ code to java + private class ExtraSpaceShareFunctionParams { +//\cond DO_NOT_DOCUMENT + internal int growableTrackCount; +//\endcond + +//\cond DO_NOT_DOCUMENT + internal float shareRatioSum; +//\endcond + +//\cond DO_NOT_DOCUMENT + internal float extraSpace; +//\endcond + + public ExtraSpaceShareFunctionParams(int growableTrackCount, float shareRatioSum, float extraSpace) { + this.growableTrackCount = growableTrackCount; + this.shareRatioSum = shareRatioSum; + this.extraSpace = extraSpace; + } + } + + // We define growth potential = limit - affected size; for base sizes, the limit + // is its growth limit. For growth limits, the limit is infinity if it is marked + // as "infinitely growable", and equal to the growth limit otherwise. + // chromium's method + // iText's comment: InfinitelyGrowableBehavior enum was replaced by ignoreInfinitelyGrowable boolean + private float GrowthPotentialForSet(GridTrackSizer.Track track, GridTrackSizer.GridItemContributionType contributionType + , bool ignoreInfinitelyGrowable) { + switch (contributionType) { + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MINIMUMS: { + if (JavaUtil.FloatCompare(track.growthLimit, -1f) == 0) { + return -1f; } - if (value.GetType() != TemplateValue.ValueType.POINT && value.GetType() != TemplateValue.ValueType.PERCENT + float increasedBaseSize = track.baseSize + track.incurredIncrease; + return track.growthLimit - increasedBaseSize; + } + + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MAXIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MAXIMUMS: { + if (!ignoreInfinitelyGrowable && JavaUtil.FloatCompare(track.growthLimit, -1f) != 0 && !track.isInfinityGrowable ) { - float added = DistributeSpaceToTrack(track, distributedSpace); - if (added > 0) { - space -= (float)added; - allFrozen = false; - } + // For growth limits, the potential is infinite if its value is infinite + // too or if the set is marked as infinitely growable; otherwise, zero. + return 0f; } + // The max track sizing function of a 'fit-content' track is treated as + // 'max-content' until it reaches the limit specified as the 'fit-content' + // argument, after which it is treated as having a fixed sizing function + // of that argument (with a growth potential of zero). + // iText's comment: Case when availableSpace is indefinite and fit-content uses percent is handled in GridTrackSizer constructor + if (track.value.GetType() == TemplateValue.ValueType.FIT_CONTENT) { + FitContentValue fitContentValue = (FitContentValue)track.value; + float growthPotential = fitContentValue.GetMaxSizeForSpace(availableSpace) - track.DefiniteGrowthLimit() - + track.incurredIncrease; + return Math.Max(growthPotential, 0); + } + // Otherwise, this set has infinite growth potential. + return -1f; } - if (allFrozen) { - break; + + case GridTrackSizer.GridItemContributionType.FOR_FREE_SPACE: { + return track.growthLimit - track.baseSize; + } + + default: { + return 0; + } + } + } + + // https://drafts.csswg.org/css-grid-2/#extra-space + // Returns true if a track's used size should be consider to grow beyond its limit + // (see the "Distribute space beyond limits" section); otherwise, false. + // Note that we will deliberately return false in cases where we don't have a + // collection of tracks different than "all affected tracks". + // chromium's method + private static bool ShouldUsedSizeGrowBeyondLimit(GridTrackSizer.Track track, GridTrackSizer.GridItemContributionType + contributionType) { + GridValue maxTrack = track.value; + if (track.value.GetType() == TemplateValue.ValueType.MINMAX) { + maxTrack = ((MinMaxValue)track.value).GetMax(); + } + switch (contributionType) { + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS: { + // intrinsic max track sizing function + return maxTrack.GetType() == TemplateValue.ValueType.MIN_CONTENT || maxTrack.GetType() == TemplateValue.ValueType + .MAX_CONTENT || maxTrack.GetType() == TemplateValue.ValueType.AUTO || maxTrack.GetType() == TemplateValue.ValueType + .FIT_CONTENT; + } + + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MINIMUMS: { + // max-content max track sizing function + return maxTrack.GetType() == TemplateValue.ValueType.MAX_CONTENT || maxTrack.GetType() == TemplateValue.ValueType + .AUTO; + } + + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MAXIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MAXIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_FREE_SPACE: { + return false; + } + + default: { + return false; } } } - // 3. Distribute space to non-affected tracks: skipped - // 4. Distribute space beyond limits: skipped + // Returns true if a track should increase its used size according to the steps in + // https://drafts.csswg.org/css-grid-2/#algo-spanning-items; false otherwise. + // + // chromium's method + private static bool IsContributionAppliedToTrack(GridTrackSizer.Track track, GridTrackSizer.GridItemContributionType + contributionType) { + GridValue minTrack = track.value; + GridValue maxTrack = track.value; + if (track.value.GetType() == TemplateValue.ValueType.MINMAX) { + minTrack = ((MinMaxValue)track.value).GetMin(); + maxTrack = ((MinMaxValue)track.value).GetMax(); + } + switch (contributionType) { + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS: { + // intrinsic min track sizing function + return minTrack.GetType() == TemplateValue.ValueType.MIN_CONTENT || minTrack.GetType() == TemplateValue.ValueType + .MAX_CONTENT || minTrack.GetType() == TemplateValue.ValueType.AUTO || minTrack.GetType() == TemplateValue.ValueType + .FIT_CONTENT; + } + + case GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS: { + // min\max-content min track sizing function + return minTrack.GetType() == TemplateValue.ValueType.MIN_CONTENT || minTrack.GetType() == TemplateValue.ValueType + .MAX_CONTENT; + } + + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MINIMUMS: { + // max-content min track sizing function + return minTrack.GetType() == TemplateValue.ValueType.MAX_CONTENT; + } + + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MAXIMUMS: { + // intrinsic max track sizing function + return maxTrack.GetType() == TemplateValue.ValueType.MIN_CONTENT || maxTrack.GetType() == TemplateValue.ValueType + .MAX_CONTENT || maxTrack.GetType() == TemplateValue.ValueType.AUTO || maxTrack.GetType() == TemplateValue.ValueType + .FIT_CONTENT; + } + + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MAXIMUMS: { + // max-content max track sizing function + return maxTrack.GetType() == TemplateValue.ValueType.MAX_CONTENT || maxTrack.GetType() == TemplateValue.ValueType + .AUTO || maxTrack.GetType() == TemplateValue.ValueType.FIT_CONTENT; + } + + case GridTrackSizer.GridItemContributionType.FOR_FREE_SPACE: { + return true; + } + + default: { + return false; + } + } + } + + // Returns the corresponding size to be increased by accommodating a grid item's + // contribution; for intrinsic min track sizing functions, return the base size. + // For intrinsic max track sizing functions, return the growth limit. + // chromium's method + private static float AffectedSizeForContribution(GridTrackSizer.Track track, GridTrackSizer.GridItemContributionType + contributionType) { + switch (contributionType) { + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_CONTENT_BASED_MINIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MINIMUMS: { + return track.baseSize; + } + + case GridTrackSizer.GridItemContributionType.FOR_INTRINSIC_MAXIMUMS: + case GridTrackSizer.GridItemContributionType.FOR_MAX_CONTENT_MAXIMUMS: { + return track.DefiniteGrowthLimit(); + } + + default: { + // FOR_FREE_SPACE not reachable here + return 0; + } + } + } + + private enum GridItemContributionType { + FOR_INTRINSIC_MINIMUMS, + FOR_CONTENT_BASED_MINIMUMS, + FOR_MAX_CONTENT_MINIMUMS, + FOR_INTRINSIC_MAXIMUMS, + FOR_MAX_CONTENT_MAXIMUMS, + FOR_FREE_SPACE + } + + // This enum corresponds to each step used to accommodate grid items across + // intrinsic tracks according to their min and max track sizing functions + // chromium's enum + // iText's method private void InitializeTrackSizes() { foreach (GridTrackSizer.Track track in tracks) { GridValue minTrackSizingValue = track.value; @@ -472,33 +924,12 @@ private void InitializeTrackSizes() { } } - /// - /// Distributes given space to track, if given space can't be fully distributed returns - /// as many space as was distributed. - /// - /// track to which distribute space - /// how much space to distribute - /// how much space was distributed - private static float DistributeSpaceToTrack(GridTrackSizer.Track track, float distributedSpace) { - if (track.growthLimit < 0 || distributedSpace + track.baseSize <= track.growthLimit) { - track.baseSize += distributedSpace; - return distributedSpace; - } - else { - if (JavaUtil.FloatCompare(track.growthLimit, track.baseSize) != 0) { - float addedToLimit = track.growthLimit - track.baseSize; - track.baseSize += addedToLimit; - return addedToLimit; - } - } - return -1.0f; - } - /// Calculate min or max contribution of a cell. /// cell to calculate contribution /// type of contribution: min if true, max otherwise /// contribution value private float CalculateMinMaxContribution(GridCell cell, bool minTypeContribution) { + // iText's method if (Grid.GridOrder.COLUMN == order) { if (cell.GetValue() is AbstractRenderer) { AbstractRenderer abstractRenderer = (AbstractRenderer)cell.GetValue(); @@ -524,6 +955,7 @@ private float CalculateMinMaxContribution(GridCell cell, bool minTypeContributio } //\cond DO_NOT_DOCUMENT + // iText's class internal class TrackSizingResult { private readonly IList tracks; @@ -587,6 +1019,7 @@ internal virtual IList GetTrackSizesAndExpandPercents(IList te //\endcond //\cond DO_NOT_DOCUMENT + // iText's class internal class Track { //\cond DO_NOT_DOCUMENT internal float baseSize; @@ -600,6 +1033,53 @@ internal class Track { //\cond DO_NOT_DOCUMENT internal GridValue value; //\endcond + +//\cond DO_NOT_DOCUMENT + internal float plannedIncrease; +//\endcond + +//\cond DO_NOT_DOCUMENT + internal float incurredIncrease; +//\endcond + +//\cond DO_NOT_DOCUMENT + internal bool isInfinityGrowable = false; +//\endcond + + public virtual float DefiniteGrowthLimit() { + // For infinite growth limits, substitute the track's base size. + return JavaUtil.FloatCompare(growthLimit, -1f) == 0 ? baseSize : growthLimit; + } + + public virtual bool IsFlexibleTrack() { + // Flex is replaced by minmax in GridTrackSizer constructor + if (value.GetType() == TemplateValue.ValueType.MINMAX) { + return ((MinMaxValue)value).GetMax().GetType() == TemplateValue.ValueType.FLEX; + } + return false; + } + + public virtual bool HasAutoMax() { + if (value.GetType() == TemplateValue.ValueType.MINMAX) { + return ((MinMaxValue)value).GetMax().GetType() == TemplateValue.ValueType.AUTO; + } + return value.GetType() == TemplateValue.ValueType.AUTO; + } + + public virtual float GetFlexFactor() { + // Flex is replaced by minmax in GridTrackSizer constructor + if (value.GetType() == TemplateValue.ValueType.MINMAX) { + BreadthValue max = ((MinMaxValue)value).GetMax(); + return max.GetType() == TemplateValue.ValueType.FLEX ? ((FlexValue)max).GetFlex() : 0f; + } + return 0f; + } + + public virtual void EnsureGrowthLimitIsNotLessThanBaseSize() { + if (JavaUtil.FloatCompare(growthLimit, -1) != 0 && growthLimit < baseSize) { + growthLimit = baseSize; + } + } } //\endcond } diff --git a/port-hash b/port-hash index d167ab831d..a361c20769 100644 --- a/port-hash +++ b/port-hash @@ -1 +1 @@ -fc414da4ec172670063a0b874ecf6af0b7629338 +79920462383c128f6e228589412e592c76db36fe