/* * * This file is part of the iText (R) project. Copyright (c) 1998-2017 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; import com.itextpdf.awt.geom.Point2D; import java.util.ArrayList; import java.util.List; /** * Represents a Bezier curve. * * @since 5.5.6 */ public class BezierCurve implements Shape { /** * If the distance between a point and a line is less than * this constant, then we consider the point lies on the line. */ public static double curveCollinearityEpsilon = 1.0e-30; /** * In the case when neither the line ((x1, y1), (x4, y4)) passes * through both (x2, y2) and (x3, y3) nor (x1, y1) = (x4, y4) we * use the square of the sum of the distances mentioned below in * compare to this field as the criterion of good approximation. * 1. The distance between the line and (x2, y2) * 2. The distance between the line and (x3, y3) */ public static double distanceToleranceSquare = 0.025D; /** * The Manhattan distance is used in the case when either the line * ((x1, y1), (x4, y4)) passes through both (x2, y2) and (x3, y3) * or (x1, y1) = (x4, y4). The essential observation is that when * the curve is a uniform speed straight line from end to end, the * control points are evenly spaced from beginning to end. Our measure * of how far we deviate from that ideal uses distance of the middle * controls: point 2 should be halfway between points 1 and 3; point 3 * should be halfway between points 2 and 4. */ public static double distanceToleranceManhattan = 0.4D; private final List<Point2D> controlPoints; /** * Constructs new bezier curve. * @param controlPoints Curve's control points. */ public BezierCurve(List<Point2D> controlPoints) { this.controlPoints = new ArrayList<Point2D>(controlPoints); } /** * {@inheritDoc} */ public List<Point2D> getBasePoints() { return controlPoints; } /** * You can adjust precision of the approximation by varying the following * parameters: {@link #curveCollinearityEpsilon}, {@link #distanceToleranceSquare}, * {@link #distanceToleranceManhattan} * * @return {@link java.util.List} containing points of piecewise linear approximation * for this bezier curve. * @since 5.5.6 */ public List<Point2D> getPiecewiseLinearApproximation() { List<Point2D> points = new ArrayList<Point2D>(); points.add(controlPoints.get(0)); recursiveApproximation(controlPoints.get(0).getX(), controlPoints.get(0).getY(), controlPoints.get(1).getX(), controlPoints.get(1).getY(), controlPoints.get(2).getX(), controlPoints.get(2).getY(), controlPoints.get(3).getX(), controlPoints.get(3).getY(), points); points.add(controlPoints.get(controlPoints.size() - 1)); return points; } // Based on the De Casteljau's algorithm private void recursiveApproximation(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, List<Point2D> points) { // Subdivision using the De Casteljau's algorithm (t = 0.5) double x12 = (x1 + x2) / 2; double y12 = (y1 + y2) / 2; double x23 = (x2 + x3) / 2; double y23 = (y2 + y3) / 2; double x34 = (x3 + x4) / 2; double y34 = (y3 + y4) / 2; double x123 = (x12 + x23) / 2; double y123 = (y12 + y23) / 2; double x234 = (x23 + x34) / 2; double y234 = (y23 + y34) / 2; double x1234 = (x123 + x234) / 2; double y1234 = (y123 + y234) / 2; double dx = x4 - x1; double dy = y4 - y1; // Constructs the line passing through (x1, y1) and (x4, y4) // |Ax2 + By2 + C|, where Ax+By+C is the equation for the line mentioned above double d2 = Math.abs(((x2 - x4) * dy - (y2 - y4) * dx)); // |Ax3 + Bx3 + C| double d3 = Math.abs(((x3 - x4) * dy - (y3 - y4) * dx)); // True if neither the line passes through both (x2, y2) and (x3, y3) // nor (x1, y1) = (x4, y4) if (d2 > curveCollinearityEpsilon || d3 > curveCollinearityEpsilon) { // True if the square of the distance between (x2, y2) and the line plus // the distance between (x3, y3) and the line is lower than the tolerance square if ((d2 + d3) * (d2 + d3) <= distanceToleranceSquare * (dx * dx + dy * dy)) { points.add(new Point2D.Double(x1234, y1234)); return; } } else { if ((Math.abs(x1 + x3 - x2 - x2) + Math.abs(y1 + y3 - y2 - y2) + Math.abs(x2 + x4 - x3 - x3) + Math.abs(y2 + y4 - y3 - y3)) <= distanceToleranceManhattan) { points.add(new Point2D.Double(x1234, y1234)); return; } } recursiveApproximation(x1, y1, x12, y12, x123, y123, x1234, y1234, points); recursiveApproximation(x1234, y1234, x234, y234, x34, y34, x4, y4, points); } }