All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.itextpdf.layout.renderer.GridTemplateResolver Maven / Gradle / Ivy

There is a newer version: 9.0.0
Show newest version
/*
    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 .
 */
package com.itextpdf.layout.renderer;

import com.itextpdf.layout.exceptions.LayoutExceptionMessageConstant;
import com.itextpdf.layout.properties.grid.AutoRepeatValue;
import com.itextpdf.layout.properties.grid.FixedRepeatValue;
import com.itextpdf.layout.properties.grid.GridValue;
import com.itextpdf.layout.properties.grid.MinMaxValue;
import com.itextpdf.layout.properties.grid.PercentValue;
import com.itextpdf.layout.properties.grid.PointValue;
import com.itextpdf.layout.properties.grid.TemplateValue;
import com.itextpdf.layout.properties.grid.TemplateValue.ValueType;

import java.util.ArrayList;
import java.util.List;
import org.slf4j.LoggerFactory;

class GridTemplateResolver {
    private final float space;
    private final float gap;
    private boolean containsIntrinsicOrFlexible = false;
    private AutoRepeatResolver autoRepeatResolver = null;
    private Result result = new Result(new ArrayList<>());

    GridTemplateResolver(float space, float gap) {
        this.space = space;
        this.gap = gap;
    }

    /**
     * Determines if auto-fit repeat was encountered during processing.
     *
     * @return true if auto-fit repeat was encountered, false otherwise
     */
    boolean isCollapseNullLines() {
        return autoRepeatResolver != null && autoRepeatResolver.repeat.isAutoFit();
    }

    /**
     * Determines how many fixed values (all template values except auto-fit/fill repeat) in the result.
     *
     * @return number of fixed values in template list
     */
    int getFixedValuesCount() {
        if (autoRepeatResolver == null) {
            return result.size();
        }
        return result.size() - (autoRepeatResolver.end - autoRepeatResolver.start);
    }

    /**
     * Shrinks template list to fit the given size by reducing number of auto-fit/fill repetitions.
     *
     * @param sizeToFit size to fit template list
     */
    List shrinkTemplatesToFitSize(int sizeToFit) {
        if (autoRepeatResolver == null) {
            return result.getList();
        }
        return autoRepeatResolver.shrinkTemplatesToFitSize(sizeToFit);
    }

    /**
     * Resolves template values to grid values by flatting repeats.
     *
     * @param template template values list
     * @return grid values list
     */
    List resolveTemplate(List template) {
        if (template == null) {
            return null;
        }
        try {
            float leftSpace = this.space;
            for (TemplateValue value : template) {
                leftSpace -= processValue(value);
                leftSpace -= gap;
            }
            leftSpace += gap;
            if (autoRepeatResolver != null) {
                if (autoRepeatResolver.start == result.size()) {
                    // This additional gap is needed when auto-repeat is located at the end of a template
                    // It's for simplifying the logic of auto-repeat, because it always adds gap after last element
                    leftSpace += gap;
                }
                autoRepeatResolver.resolve(leftSpace);
            }
            return result.getList();
        } catch (IllegalStateException exception) {
            LoggerFactory.getLogger(GridTemplateResolver.class).warn(exception.getMessage());
            reset();
        }
        return null;
    }

    private float processValue(TemplateValue value) {
        switch (value.getType()) {
            case MIN_CONTENT:
            case MAX_CONTENT:
            case AUTO:
            case FLEX:
            case FIT_CONTENT:
                result.addValue((GridValue) value);
                containsIntrinsicOrFlexible = true;
                break;
            case POINT:
                result.addValue((GridValue) value);
                return ((PointValue) value).getValue();
            case PERCENT:
                result.addValue((GridValue) value);
                return space > 0.0f ? ((PercentValue) value).getValue() / 100 * space : 0.0f;
            case MINMAX:
                result.addValue((GridValue) value);
                result.setFreeze(true);
                // Treating each track as its max track sizing function if that is definite
                // or as its minimum track sizing function otherwise
                // if encountered intrinsic or flexible before, then it doesn't matter what to process
                boolean currentValue = containsIntrinsicOrFlexible;
                final MinMaxValue minMaxValue = (MinMaxValue) value;
                if (minMaxValue.getMin().getType() == ValueType.FLEX) {
                    // A future level of CSS Grid spec may allow  minimums, but not now
                    throw new IllegalStateException(
                            LayoutExceptionMessageConstant.FLEXIBLE_ARENT_ALLOWED_AS_MINIMUM_IN_MINMAX);
                }
                float length = processValue(minMaxValue.getMax());
                if (containsIntrinsicOrFlexible) {
                    length = processValue(minMaxValue.getMin());
                }
                containsIntrinsicOrFlexible = currentValue;
                result.setFreeze(false);
                return length;
            case FIXED_REPEAT:
                float usedSpace = 0.0f;
                FixedRepeatValue repeat = (FixedRepeatValue) value;
                for (int i = 0; i < repeat.getRepeatCount(); ++i) {
                    for (GridValue element : repeat.getValues()) {
                        usedSpace += processValue(element);
                    }
                    usedSpace += (repeat.getValues().size() - 1) * gap;
                }
                return usedSpace;
            case AUTO_REPEAT:
                if (autoRepeatResolver != null) {
                    throw new IllegalStateException(LayoutExceptionMessageConstant.GRID_AUTO_REPEAT_CAN_BE_USED_ONLY_ONCE);
                }
                autoRepeatResolver = new AutoRepeatResolver((AutoRepeatValue) value, result.size());
                break;
        }
        return 0.0f;
    }

    private void reset() {
        autoRepeatResolver = null;
        result.getList().clear();
        result.setInsertPoint(-1);
    }

    private class AutoRepeatResolver {
        final AutoRepeatValue repeat;
        final int start;
        int end = -1;

        AutoRepeatResolver(AutoRepeatValue repeat, int pos) {
            this.repeat = repeat;
            this.start = pos;
        }

        /**
         * Resolves auto-fit/fill repeat if it was encountered.
         * If given space is less than 0, only one iteration will be performed.
         *
         * @param leftSpace space to fit repeat values on
         */
        public void resolve(float leftSpace) {
            float usedSpace = 0.0f;
            float usedSpacePerIteration = -1.0f;
            int fixedTemplatesCount = result.size();
            do {
                result.setInsertPoint(start);
                for (GridValue value : repeat.getValues()) {
                    usedSpace += processValue(value);
                    usedSpace += gap;
                }
                if (usedSpacePerIteration < 0.0f) {
                    usedSpacePerIteration = usedSpace;
                }
                if (containsIntrinsicOrFlexible) {
                    throw new IllegalStateException(
                            LayoutExceptionMessageConstant.GRID_AUTO_REPEAT_CANNOT_BE_COMBINED_WITH_INDEFINITE_SIZES);
                }
            } while (usedSpace + usedSpacePerIteration <= leftSpace);
            end = start + result.size() - fixedTemplatesCount;
        }

        /**
         * Shrinks template list to fit the given size by reducing number of auto-fit/fill repetitions.
         *
         * @param sizeToFit size to fit template list
         */
        List shrinkTemplatesToFitSize(int sizeToFit) {
            // Getting max number of available repetitions
            final int allowedRepeatValuesCount = getAllowedRepeatValuesCount(sizeToFit);

            // It could be done with .subList(), but this is not portable on .NET
            List shrankResult = new ArrayList<>(result.size());
            List previousResult = result.getList();
            for (int i = 0; i < start; ++i) {
                shrankResult.add(previousResult.get(i));
            }
            for (int i = 0; i < allowedRepeatValuesCount; ++i) {
                shrankResult.addAll(repeat.getValues());
            }
            for (int i = end; i < previousResult.size(); ++i) {
                shrankResult.add(previousResult.get(i));
            }
            result = new Result(shrankResult);
            return result.getList();
        }

        private int getAllowedRepeatValuesCount(int sizeToFit) {
            // int division with rounding down
            int allowedRepeatValuesCount =
                    (Math.min(sizeToFit - getFixedValuesCount(), end - start))
                    / repeat.getValues().size()
                    * repeat.getValues().size();

            // if space was indefinite than repeat can be used only once
            if (space < 0.0f && allowedRepeatValuesCount > 0) {
                allowedRepeatValuesCount = 1;
            }
            return allowedRepeatValuesCount;
        }
    }

    private static class Result {
        final List result;
        int insertPoint = -1;
        boolean freeze = false;

        Result(List result) {
            this.result = result;
        }

        public void addValue(GridValue value) {
            if (freeze) {
                return;
            }
            if (insertPoint < 0) {
                result.add( value);
            } else {
                result.add(insertPoint++, value);
            }
        }

        public void setInsertPoint(int insertPoint) {
            this.insertPoint = insertPoint;
        }

        public int size() {
            return result.size();
        }

        public List getList() {
            return result;
        }

        public void setFreeze(boolean freeze) {
            this.freeze = freeze;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy