iText 5 itextpdf.jar Source Code

itextpdf.jar is a component in iText 5 Java library to provide core functionalities. iText Java library allows you to generate and manage PDF documents.

The Source Code files are provided at iText GitHub site.

You can compile it to generate your JAR file, using pom.xml as the build configuration file.

The source code of itextpdf-5.5.14.jar is provided below:

✍: FYIcenter.com

com/itextpdf/text/pdf/parser/clipper/ClipperOffset.java

/*
 *
 * This file is part of the iText (R) project.
    Copyright (c) 1998-2020 iText Group NV
 * Authors: Bruno Lowagie, Paulo Soares, et al.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3
 * as published by the Free Software Foundation with the addition of the
 * following permission added to Section 15 as permitted in Section 7(a):
 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY
 * ITEXT GROUP. ITEXT GROUP DISCLAIMS THE WARRANTY OF NON INFRINGEMENT
 * OF THIRD PARTY RIGHTS
 *
 * 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 http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
 * http://itextpdf.com/terms-of-use/
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License,
 * a covered work must retain the producer line in every PDF that is created
 * or manipulated using iText.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license. Buying such a license is mandatory as soon as you
 * develop commercial activities involving the iText software without
 * disclosing the source code of your own applications.
 * These activities include: offering paid services to customers as an ASP,
 * serving PDFs on the fly in a web application, shipping iText with a closed
 * source product.
 *
 * For more information, please contact iText Software Corp. at this
 * address: sales@itextpdf.com
 */
package com.itextpdf.text.pdf.parser.clipper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import com.itextpdf.text.pdf.parser.clipper.Clipper.ClipType;
import com.itextpdf.text.pdf.parser.clipper.Clipper.EndType;
import com.itextpdf.text.pdf.parser.clipper.Clipper.JoinType;
import com.itextpdf.text.pdf.parser.clipper.Clipper.PolyFillType;
import com.itextpdf.text.pdf.parser.clipper.Clipper.PolyType;
import com.itextpdf.text.pdf.parser.clipper.Point.DoublePoint;
import com.itextpdf.text.pdf.parser.clipper.Point.LongPoint;

public class ClipperOffset {
    private static boolean nearZero( double val ) {
        return val > -TOLERANCE && val < TOLERANCE;
    }

    private Paths destPolys;
    private Path srcPoly;
    private Path destPoly;

    private final List<DoublePoint> normals;
    private double delta, inA, sin, cos;

    private double miterLim, stepsPerRad;
    private LongPoint lowest;

    private final PolyNode polyNodes;
    private final double arcTolerance;

    private final double miterLimit;
    private final static double TWO_PI = Math.PI * 2;

    private final static double DEFAULT_ARC_TOLERANCE = 0.25;

    private final static double TOLERANCE = 1.0E-20;

    public ClipperOffset() {
        this( 2, DEFAULT_ARC_TOLERANCE );
    }

    public ClipperOffset(double miterLimit) {
        this(miterLimit, DEFAULT_ARC_TOLERANCE);
    }

    public ClipperOffset( double miterLimit, double arcTolerance ) {
        this.miterLimit = miterLimit;
        this.arcTolerance = arcTolerance;
        lowest = new LongPoint();
        lowest.setX( -1l );
        polyNodes = new PolyNode();
        normals = new ArrayList<DoublePoint>();
    }

    public void addPath( Path path, JoinType joinType, EndType endType ) {
        int highI = path.size() - 1;
        if (highI < 0) {
            return;
        }
        final PolyNode newNode = new PolyNode();
        newNode.setJoinType( joinType );
        newNode.setEndType( endType );

        //strip duplicate points from path and also get index to the lowest point ...
        if (endType == EndType.CLOSED_LINE || endType == EndType.CLOSED_POLYGON) {
            while (highI > 0 && path.get( 0 ) == path.get( highI )) {
                highI--;
            }
        }

        newNode.getPolygon().add( path.get( 0 ) );
        int j = 0, k = 0;
        for (int i = 1; i <= highI; i++) {
            if (newNode.getPolygon().get( j ) != path.get( i )) {
                j++;
                newNode.getPolygon().add( path.get( i ) );
                if (path.get( i ).getY() > newNode.getPolygon().get( k ).getY() || path.get( i ).getY() == newNode.getPolygon().get( k ).getY()
                                && path.get( i ).getX() < newNode.getPolygon().get( k ).getX()) {
                    k = j;
                }
            }
        }
        if (endType == EndType.CLOSED_POLYGON && j < 2) {
            return;
        }

        polyNodes.addChild( newNode );

        //if this path's lowest pt is lower than all the others then update m_lowest
        if (endType != EndType.CLOSED_POLYGON) {
            return;
        }
        if (lowest.getX() < 0) {
            lowest = new LongPoint( polyNodes.getChildCount() - 1, k );
        }
        else {
            final LongPoint ip = polyNodes.getChilds().get( (int) lowest.getX() ).getPolygon().get( (int) lowest.getY() );
            if (newNode.getPolygon().get( k ).getY() > ip.getY() || newNode.getPolygon().get( k ).getY() == ip.getY()
                            && newNode.getPolygon().get( k ).getX() < ip.getX()) {
                lowest = new LongPoint( polyNodes.getChildCount() - 1, k );
            }
        }
    }

    public void addPaths( Paths paths, JoinType joinType, EndType endType ) {
        for (final Path p : paths) {
            addPath( p, joinType, endType );
        }
    }

    public void clear() {
        polyNodes.getChilds().clear();
        lowest.setX( -1l );
    }

    private void doMiter( int j, int k, double r ) {
        final double q = delta / r;
        destPoly.add( new LongPoint(  Math.round( srcPoly.get( j ).getX() + (normals.get( k ).getX() + normals.get( j ).getX()) * q ), Math
                        .round( srcPoly.get( j ).getY() + (normals.get( k ).getY() + normals.get( j ).getY()) * q ) ) );
    }

    private void doOffset( double delta ) {
        destPolys = new Paths();
        this.delta = delta;

        //if Zero offset, just copy any CLOSED polygons to m_p and return ...
        if (nearZero( delta )) {
            for (int i = 0; i < polyNodes.getChildCount(); i++) {
                final PolyNode node = polyNodes.getChilds().get( i );
                if (node.getEndType() == EndType.CLOSED_POLYGON) {
                    destPolys.add( node.getPolygon() );
                }
            }
            return;
        }

        //see offset_triginometry3.svg in the documentation folder ...
        if (miterLimit > 2) {
            miterLim = 2 / (miterLimit * miterLimit);
        }
        else {
            miterLim = 0.5;
        }

        double y;
        if (arcTolerance <= 0.0) {
            y = DEFAULT_ARC_TOLERANCE;
        }
        else if (arcTolerance > Math.abs( delta ) * DEFAULT_ARC_TOLERANCE) {
            y = Math.abs( delta ) * DEFAULT_ARC_TOLERANCE;
        }
        else {
            y = arcTolerance;
        }
        //see offset_triginometry2.svg in the documentation folder ...
        final double steps = Math.PI / Math.acos( 1 - y / Math.abs( delta ) );
        sin = Math.sin( TWO_PI / steps );
        cos = Math.cos( TWO_PI / steps );
        stepsPerRad = steps / TWO_PI;
        if (delta < 0.0) {
            sin = -sin;
        }

        for (int i = 0; i < polyNodes.getChildCount(); i++) {
            final PolyNode node = polyNodes.getChilds().get( i );
            srcPoly = node.getPolygon();

            final int len = srcPoly.size();

            if (len == 0 || (delta <= 0 && (len < 3 || node.getEndType() != EndType.CLOSED_POLYGON))) {
                continue;
            }

            destPoly = new Path();

            if (len == 1) {
                if (node.getJoinType() == JoinType.ROUND) {
                    double X = 1.0, Y = 0.0;
                    for (int j = 1; j <= steps; j++) {
                        destPoly.add( new LongPoint( Math.round( srcPoly.get( 0 ).getX() + X * delta ), Math.round( srcPoly.get( 0 ).getY() + Y
                                        * delta ) ) );
                        final double X2 = X;
                        X = X * cos - sin * Y;
                        Y = X2 * sin + Y * cos;
                    }
                }
                else {
                    double X = -1.0, Y = -1.0;
                    for (int j = 0; j < 4; ++j) {
                        destPoly.add( new LongPoint( Math.round( srcPoly.get( 0 ).getX() + X * delta ), Math.round( srcPoly.get( 0 ).getY() + Y
                                        * delta ) ) );
                        if (X < 0) {
                            X = 1;
                        }
                        else if (Y < 0) {
                            Y = 1;
                        }
                        else {
                            X = -1;
                        }
                    }
                }
                destPolys.add( destPoly );
                continue;
            }

            //build m_normals ...
            normals.clear();
            for (int j = 0; j < len - 1; j++) {
                normals.add( Point.getUnitNormal( srcPoly.get( j ), srcPoly.get( j + 1 ) ) );
            }
            if (node.getEndType() == EndType.CLOSED_LINE || node.getEndType() == EndType.CLOSED_POLYGON) {
                normals.add( Point.getUnitNormal( srcPoly.get( len - 1 ), srcPoly.get( 0 ) ) );
            }
            else {
                normals.add( new DoublePoint( normals.get( len - 2 ) ) );
            }

            if (node.getEndType() == EndType.CLOSED_POLYGON) {
                final int[] k = new int[] { len - 1 };
                for (int j = 0; j < len; j++) {
                    offsetPoint( j, k, node.getJoinType() );
                }
                destPolys.add( destPoly );
            }
            else if (node.getEndType() == EndType.CLOSED_LINE) {
                final int[] k = new int[] { len - 1 };
                for (int j = 0; j < len; j++) {
                    offsetPoint( j, k, node.getJoinType() );
                }
                destPolys.add( destPoly );
                destPoly = new Path();
                //re-build m_normals ...
                final DoublePoint n = normals.get( len - 1 );
                for (int j = len - 1; j > 0; j--) {
                    normals.set( j, new DoublePoint( -normals.get( j - 1 ).getX(), -normals.get( j - 1 ).getY() ) );
                }
                normals.set( 0, new DoublePoint( -n.getX(), -n.getY(), 0 ) );
                k[0] = 0;
                for (int j = len - 1; j >= 0; j--) {
                    offsetPoint( j, k, node.getJoinType() );
                }
                destPolys.add( destPoly );
            }
            else {
                final int[] k = new int[1];
                for (int j = 1; j < len - 1; ++j) {
                    offsetPoint( j, k, node.getJoinType() );
                }

                LongPoint pt1;
                if (node.getEndType() == EndType.OPEN_BUTT) {
                    final int j = len - 1;
                    pt1 = new LongPoint( Math.round( srcPoly.get( j ).getX() + normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j )
                                    .getY() + normals.get( j ).getY() * delta ), 0 );
                    destPoly.add( pt1 );
                    pt1 = new LongPoint( Math.round( srcPoly.get( j ).getX() - normals.get( j ).getX() * delta ), Math.round( srcPoly.get( j )
                                    .getY() - normals.get( j ).getY() * delta ), 0 );
                    destPoly.add( pt1 );
                }
                else {
                    final int j = len - 1;
                    k[0] = len - 2;
                    inA = 0;
                    normals.set( j, new DoublePoint( -normals.get( j ).getX(), -normals.get( j ).getY() ) );
                    if (node.getEndType() == EndType.OPEN_SQUARE) {
                        doSquare( j, k[0], true );
                    }
                    else {
                        doRound( j, k[0] );
                    }
                }

                //re-build m_normals ...
                for (int j = len - 1; j > 0; j--) {
                    normals.set( j, new DoublePoint( -normals.get( j - 1 ).getX(), -normals.get( j - 1 ).getY() ) );
                }

                normals.set( 0, new DoublePoint( -normals.get( 1 ).getX(), -normals.get( 1 ).getY() ) );

                k[0] = len - 1;
                for (int j = k[0] - 1; j > 0; --j) {
                    offsetPoint( j, k, node.getJoinType() );
                }

                if (node.getEndType() == EndType.OPEN_BUTT) {
                    pt1 = new LongPoint(  Math.round( srcPoly.get( 0 ).getX() - normals.get( 0 ).getX() * delta ),  Math.round( srcPoly.get( 0 )
                                    .getY() - normals.get( 0 ).getY() * delta ) );
                    destPoly.add( pt1 );
                    pt1 = new LongPoint(  Math.round( srcPoly.get( 0 ).getX() + normals.get( 0 ).getX() * delta ),  Math.round( srcPoly.get( 0 )
                                    .getY() + normals.get( 0 ).getY() * delta ) );
                    destPoly.add( pt1 );
                }
                else {
                    k[0] = 1;
                    inA = 0;
                    if (node.getEndType() == EndType.OPEN_SQUARE) {
                        doSquare( 0, 1, true );
                    }
                    else {
                        doRound( 0, 1 );
                    }
                }
                destPolys.add( destPoly );
            }
        }
    }

    private void doRound( int j, int k ) {
        final double a = Math.atan2( inA, normals.get( k ).getX() * normals.get( j ).getX() + normals.get( k ).getY() * normals.get( j ).getY() );
        final int steps = Math.max( (int) Math.round( stepsPerRad * Math.abs( a ) ), 1 );

        double X = normals.get( k ).getX(), Y = normals.get( k ).getY(), X2;
        for (int i = 0; i < steps; ++i) {
            destPoly.add( new LongPoint(  Math.round( srcPoly.get( j ).getX() + X * delta ),  Math.round( srcPoly.get( j ).getY() + Y * delta ) ) );
            X2 = X;
            X = X * cos - sin * Y;
            Y = X2 * sin + Y * cos;
        }
        destPoly.add( new LongPoint(  Math.round( srcPoly.get( j ).getX() + normals.get( j ).getX() * delta ),  Math.round( srcPoly.get( j ).getY()
                        + normals.get( j ).getY() * delta ) ) );
    }

    private void doSquare( int j, int k, boolean addExtra ) {
        final double nkx = normals.get( k ).getX();
        final double nky = normals.get( k ).getY();
        final double njx = normals.get( j ).getX();
        final double njy = normals.get( j ).getY();
        final double sjx = srcPoly.get( j ).getX();
        final double sjy = srcPoly.get( j ).getY();
        final double dx = Math.tan( Math.atan2( inA, nkx * njx + nky * njy ) / 4 );
        destPoly.add( new LongPoint(  Math.round( sjx + delta * (nkx - (addExtra ? nky * dx : 0)) ),  Math.round( sjy + delta * (nky + (addExtra ? nkx * dx : 0)) ), 0 ) );
        destPoly.add( new LongPoint(  Math.round( sjx + delta * (njx + (addExtra ? njy * dx : 0)) ),  Math.round( sjy + delta * (njy - (addExtra ? njx * dx : 0)) ), 0 ) );
    }

    //------------------------------------------------------------------------------

    public void execute( Paths solution, double delta ) {
        solution.clear();
        fixOrientations();
        doOffset( delta );
        //now clean up 'corners' ...
        final DefaultClipper clpr = new DefaultClipper( Clipper.REVERSE_SOLUTION );
        clpr.addPaths( destPolys, PolyType.SUBJECT, true );
        if (delta > 0) {
            clpr.execute( ClipType.UNION, solution, PolyFillType.POSITIVE, PolyFillType.POSITIVE );
        }
        else {
            final LongRect r = destPolys.getBounds();
            final Path outer = new Path( 4 );

            outer.add( new LongPoint( r.left - 10, r.bottom + 10, 0 ) );
            outer.add( new LongPoint( r.right + 10, r.bottom + 10, 0 ) );
            outer.add( new LongPoint( r.right + 10, r.top - 10, 0 ) );
            outer.add( new LongPoint( r.left - 10, r.top - 10, 0 ) );

            clpr.addPath( outer, PolyType.SUBJECT, true );

            clpr.execute( ClipType.UNION, solution, PolyFillType.NEGATIVE, PolyFillType.NEGATIVE );
            if (solution.size() > 0) {
                solution.remove( 0 );
            }
        }
    }

    //------------------------------------------------------------------------------

    public void execute( PolyTree solution, double delta ) {
        solution.Clear();
        fixOrientations();
        doOffset( delta );

        //now clean up 'corners' ...
        final DefaultClipper clpr = new DefaultClipper( Clipper.REVERSE_SOLUTION );
        clpr.addPaths( destPolys, PolyType.SUBJECT, true );
        if (delta > 0) {
            clpr.execute( ClipType.UNION, solution, PolyFillType.POSITIVE, PolyFillType.POSITIVE );
        }
        else {
            final LongRect r = destPolys.getBounds();
            final Path outer = new Path( 4 );

            outer.add( new LongPoint( r.left - 10, r.bottom + 10, 0 ) );
            outer.add( new LongPoint( r.right + 10, r.bottom + 10, 0 ) );
            outer.add( new LongPoint( r.right + 10, r.top - 10, 0 ) );
            outer.add( new LongPoint( r.left - 10, r.top - 10, 0 ) );

            clpr.addPath( outer, PolyType.SUBJECT, true );

            clpr.execute( ClipType.UNION, solution, PolyFillType.NEGATIVE, PolyFillType.NEGATIVE );
            //remove the outer PolyNode rectangle ...
            if (solution.getChildCount() == 1 && solution.getChilds().get( 0 ).getChildCount() > 0) {
                final PolyNode outerNode = solution.getChilds().get( 0 );
                solution.getChilds().set( 0, outerNode.getChilds().get( 0 ) );
                solution.getChilds().get( 0 ).setParent( solution );
                for (int i = 1; i < outerNode.getChildCount(); i++) {
                    solution.addChild( outerNode.getChilds().get( i ) );
                }
            }
            else {
                solution.Clear();
            }
        }
    }

    //------------------------------------------------------------------------------

    private void fixOrientations() {
        //fixup orientations of all closed paths if the orientation of the
        //closed path with the lowermost vertex is wrong ...
        if (lowest.getX() >= 0 && !polyNodes.childs.get( (int) lowest.getX() ).getPolygon().orientation()) {
            for (int i = 0; i < polyNodes.getChildCount(); i++) {
                final PolyNode node = polyNodes.childs.get( i );
                if (node.getEndType() == EndType.CLOSED_POLYGON || node.getEndType() == EndType.CLOSED_LINE && node.getPolygon().orientation()) {
                    Collections.reverse( node.getPolygon() );

                }
            }
        }
        else {
            for (int i = 0; i < polyNodes.getChildCount(); i++) {
                final PolyNode node = polyNodes.childs.get( i );
                if (node.getEndType() == EndType.CLOSED_LINE && !node.getPolygon().orientation()) {
                    Collections.reverse( node.getPolygon() );
                }
            }
        }
    }

    private void offsetPoint( int j, int[] kV, JoinType jointype ) {
        //cross product ...
        final int k = kV[0];
        final double nkx = normals.get( k ).getX();
        final double nky = normals.get( k ).getY();
        final double njy = normals.get( j ).getY();
        final double njx = normals.get( j ).getX();
        final long sjx = srcPoly.get( j ).getX();
        final long sjy = srcPoly.get( j ).getY();
        inA = nkx * njy - njx * nky;

        if (Math.abs( inA * delta ) < 1.0) {
            //dot product ...

            final double cosA = nkx * njx + njy * nky;
            if (cosA > 0) // angle ==> 0 degrees
            {
                destPoly.add( new LongPoint(  Math.round( sjx + nkx * delta ),  Math.round( sjy + nky * delta ), 0 ) );
                return;
            }
            //else angle ==> 180 degrees
        }
        else if (inA > 1.0) {
            inA = 1.0;
        }
        else if (inA < -1.0) {
            inA = -1.0;
        }

        if (inA * delta < 0) {
            destPoly.add( new LongPoint(  Math.round( sjx + nkx * delta ),  Math.round( sjy + nky * delta ) ) );
            destPoly.add( srcPoly.get( j ) );
            destPoly.add( new LongPoint(  Math.round( sjx + njx * delta ),  Math.round( sjy + njy * delta ) ) );
        }
        else {
            switch (jointype) {
                case MITER: {
                    final double r = 1 + njx * nkx + njy * nky;
                    if (r >= miterLim) {
                        doMiter( j, k, r );
                    }
                    else {
                        doSquare( j, k, false );
                    }
                    break;
                }
                case BEVEL:
                    doSquare( j, k, false );
                    break;
                case ROUND:
                    doRound( j, k );
                    break;
            }
        }
        kV[0] = j;
    }
    //------------------------------------------------------------------------------
}

com/itextpdf/text/pdf/parser/clipper/ClipperOffset.java

 

⇒ iText-2.1.6.jar - iText, a JAVA-PDF library

⇐ iText layout.jar Source Code

⇑ Download and Install iText Java Library

⇑⇑ iText for PDF Generation

2021-07-03, 47945👍, 0💬