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

com.itextpdf.layout.renderer.GridView 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.properties.grid.GridFlow;

/**
 * This class represents a view on a grid, which returns cells one by one in a specified order.
 * Also it allows to place a cell on a grid in this specific order and resizes the grid if needed.
 */
class GridView {
    private final Grid grid;
    private final Grid.GridOrder iterationOrder;
    private final Cursor cursor;
    private boolean restrictYGrow = false;
    private boolean restrictXGrow = false;
    private boolean hasNext = true;
    private int rightMargin;
    private int bottomMargin;

    GridView(Grid grid, GridFlow iterationOrder) {
        this.iterationOrder = (GridFlow.COLUMN.equals(iterationOrder) || GridFlow.COLUMN_DENSE.equals(iterationOrder))
                ? Grid.GridOrder.COLUMN : Grid.GridOrder.ROW;
        this.cursor = new Cursor(GridFlow.ROW_DENSE.equals(iterationOrder)
                || GridFlow.COLUMN_DENSE.equals(iterationOrder));
        this.grid = grid;
    }

    public boolean hasNext() {
        return cursor.y < grid.getRows().length - bottomMargin
                && cursor.x < grid.getRows()[0].length - rightMargin
                && hasNext;
    }

    public Pos next() {
        //If cell has fixed both x and y, then no need to iterate over the grid.
        if (isFixed()) {
            hasNext = false;
        } else if (restrictXGrow) {
            //If cell has fixed x position, then only view's 'y' can be moved.
            ++cursor.y;
            return new Pos(cursor);
        } else if (restrictYGrow) {
            //If cell has fixed y position, then only view's 'x' can be moved.
            ++cursor.x;
            return new Pos(cursor);
        }
        //If current flow is row, then grid should be iterated row by row, so iterate 'x' first, then 'y'
        //If current flow is column, then grid should be iterated column by column, so iterate 'y' first, then 'x'
        Pos boundaries = new Pos(grid.getRows().length - bottomMargin, grid.getRows()[0].length - rightMargin);
        cursor.increment(iterationOrder, boundaries);
        return new Pos(cursor);
    }

    /**
     * Resets grid view and sets it up for processing new element
     * If sparse algorithm is used then x and y positions on a grid are not reset.
     *
     * @param y left upper corner y position of an element on the grid
     * @param x left upper corner x position of an element on the grid
     * @param rightMargin specifies how many cells not to process at the right end of a grid
     * @param bottomMargin specifies how many cells not to process at the bottom end of a grid
     * @return first element position
     */
    Pos reset(int y, int x, int rightMargin, int bottomMargin) {
        this.cursor.setY(y);
        this.cursor.setX(x);
        if (x == -1 && y == -1) {
            if (rightMargin > grid.getNumberOfColumns() - this.cursor.x
                    + (Grid.GridOrder.COLUMN.equals(iterationOrder) ? 1 : 0)) {
                this.cursor.setX(0);
            }
            if (bottomMargin > grid.getNumberOfRows() - this.cursor.y
                    + (Grid.GridOrder.ROW.equals(iterationOrder) ? 1 : 0)) {
                this.cursor.setY(0);
            }
        }
        this.rightMargin = rightMargin - 1;
        this.bottomMargin = bottomMargin - 1;
        this.restrictXGrow = x != -1;
        this.restrictYGrow = y != -1;
        this.hasNext = true;
        return new Pos(cursor);
    }

    /**
     * Try to fit cell at the current position.
     * If cell is fit, then update current flow cursor axis by width/height of a laid out cell.
     *
     * @param width width of the cell
     * @param height height of the cell
     * @return true if cell is fit, false otherwise
     */
    boolean fit(int width, int height) {
        final GridCell[][] rows = grid.getRows();
        for (int i = cursor.x; i < cursor.x + width; ++i) {
            for (int j = cursor.y; j < cursor.y + height; ++j) {
                if (rows[j][i] != null) {
                    return false;
                }
            }
        }
        increaseDefaultCursor(new Pos(height, width));
        resetCursorIfIntersectingCellIsPlaced();
        return true;
    }

    /**
     * Reset cursor to the start of a grid if placed a cell, which intersects current flow axis.
     * This is needed because such cells should be placed out of order and it's expected that
     * they are go first while constructing the grid.
     */
    void resetCursorIfIntersectingCellIsPlaced() {
        if ((Grid.GridOrder.ROW.equals(iterationOrder) && restrictYGrow)
        || (Grid.GridOrder.COLUMN.equals(iterationOrder) && restrictXGrow)) {
            this.cursor.reset();
        }
    }

    /**
     * Increase cursor in current flow axis
     *
     * @param cellSizes cell width and height values
     */
    void increaseDefaultCursor(Pos cellSizes) {
        if (Grid.GridOrder.ROW.equals(iterationOrder)) {
            cursor.x += cellSizes.x - 1;
        } else if (Grid.GridOrder.COLUMN.equals(iterationOrder)) {
            cursor.y += cellSizes.y - 1;
        }

    }

    void increaseDefaultAxis() {
        if (restrictYGrow) {
            grid.resize(-1, grid.getRows()[0].length + 1);
        } else if (restrictXGrow) {
            grid.resize(grid.getRows().length + 1, -1);
        } else if (Grid.GridOrder.ROW.equals(iterationOrder)) {
            grid.resize(grid.getRows().length + 1, -1);
        } else if (Grid.GridOrder.COLUMN.equals(iterationOrder)) {
            grid.resize(-1, grid.getRows()[0].length + 1);
        }
        hasNext = true;
    }

    /**
     * Determines if current grid view can be iterated.
     *
     * @return {@code true} if fixed {@code false} otherwise
     */
    boolean isFixed() {
        return restrictXGrow && restrictYGrow;
    }

    /**
     * Represents position on a grid.
     */
    static class Pos {
        /**
         * column index.
         */
        protected int x;
        /**
         * row index.
         */
        protected int y;

        /**
         * Creates a position from two integers.
         *
         * @param y row index.
         * @param x column index.
         */
        public Pos(int y, int x) {
            this.y = y;
            this.x = x;
        }

        /**
         * Creates a position from other position instance.
         *
         * @param other other position
         */
        public Pos(Pos other) {
            this.y = other.y;
            this.x = other.x;
        }

        public int getX() {
            return x;
        }

        public int getY() {
            return y;
        }
    }

    /**
     * Represents a placement cursor.
     */
    static class Cursor extends Pos {
        //Determines whether to use "dense" or "sparse" packing algorithm
        private final boolean densePacking;

        /**
         * Create new placement cursor with either sparse or dense placement algorithm.
         *
         * @param densePacking true to use "dense", false to use "sparse" placement algorithm
         */
        public Cursor(boolean densePacking) {
            super(0, 0);
            this.densePacking = densePacking;
        }

        public void setX(int x) {
            if (densePacking) {
                this.x = Math.max(x, 0);
            } else if (this.x > x && x != -1) {
                //Special case for sparse algorithm
                //if provided 'x' less than cursor 'x' then increase cursor's 'y'
                this.x = x;
                ++this.y;
            } else {
                this.x = Math.max(x, this.x);
            }
        }

        public void setY(int y) {
            if (densePacking) {
                this.y = Math.max(y, 0);
            } else if (this.y > y && y != -1) {
                //Special case for sparse algorithm
                //if provided 'y' less than cursor 'y' then increase cursor's 'x'
                this.y = y;
                ++this.x;
            } else {
                this.y = Math.max(y, this.y);
            }
        }

        /**
         * Increment cursor in specified flow axis and if it exceeds the boundary in that axis
         * make a carriage return.
         *
         * @param flow flow which determines in which axis cursor will be increased
         * @param boundaries grid view boundaries
         */
        public void increment(Grid.GridOrder flow, Pos boundaries) {
            if (Grid.GridOrder.ROW.equals(flow)) {
                ++x;
                if (x == boundaries.x) {
                    x = 0;
                    ++y;
                }
            } else if (Grid.GridOrder.COLUMN.equals(flow)) {
                ++y;
                if (y == boundaries.y) {
                    y = 0;
                    ++x;
                }
            }
        }

        public void reset() {
            this.x = 0;
            this.y = 0;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy