Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- public class CBorder {
- private static final double ANGLE_180_DEG = Math.PI;
- private static final double ANGLE_90_DEG = Math.PI / 2;
- private static final double ANGLE_34_DEG = Math.toRadians(34);
- private static final double ANGLE_12_DEG = Math.toRadians(12);
- private final PdfAppearance cs;
- private final Rectangle annotRect;
- private final double intensity;
- private final double lineWidth;
- private Rectangle rectWithDiff;
- private boolean outputStarted = false;
- private double bboxMinX;
- private double bboxMinY;
- private double bboxMaxX;
- private double bboxMaxY;
- public CBorder(PdfAppearance stream, double intensity, double lineWidth, Rectangle rect) {
- this.cs = stream;
- this.intensity = intensity;
- this.lineWidth = lineWidth;
- this.annotRect = rect;
- }
- /**
- * Creates a cloudy border for a rectangular annotation. The rectangle is
- * specified by the <code>RD</code> entry and the <code>Rect</code> entry
- * that was passed in to the constructor.
- * <p>
- * This can be used for Square and FreeText annotations. However, this does
- * not produce the text and the callout line for FreeTexts.
- *
- * @param rd entry <code>RD</code>, or null if the entry does not exist
- * @throws IOException If there is an error writing to the stream.
- */
- public void createCloudyRectangle(Rectangle rd) throws IOException {
- rectWithDiff = applyRectDiff(rd, lineWidth / 2);
- double left = rectWithDiff.getLeft();
- double bottom = rectWithDiff.getBottom();
- double right = rectWithDiff.getRight();
- double top = rectWithDiff.getTop();
- cloudyRectangleImpl(left, bottom, right, top, false);
- finish();
- }
- /**
- * Creates a cloudy border for a Polygon annotation.
- *
- * @param path polygon path
- * @throws IOException If there is an error writing to the stream.
- */
- public void createCloudyPolygon(float[][] path) throws IOException {
- int n = path.length;
- Point2D.Double[] polygon = new Point2D.Double[n];
- for (int i=0; i<n; i++) {
- float[] array = path[i];
- if (array.length == 2) {
- polygon[i] = new Point2D.Double(array[0], array[1]);
- }
- else if (array.length == 6) {
- // TODO Curve segments are not yet supported in cloudy border.
- polygon[i] = new Point2D.Double(array[4], array[5]);
- }
- }
- cloudyPolygonImpl(polygon, false);
- finish();
- }
- /**
- * Creates a cloudy border for a Circle annotation. The ellipse is specified
- * by the <code>RD</code> entry and the <code>Rect</code> entry that was
- * passed in to the constructor.
- *
- * @param rd entry <code>RD</code>, or null if the entry does not exist
- * @throws IOException If there is an error writing to the stream.
- */
- public void createCloudyEllipse(Rectangle rd) throws IOException {
- rectWithDiff = applyRectDiff(rd, 0);
- double left = rectWithDiff.getLeft();
- double bottom = rectWithDiff.getBottom();
- double right = rectWithDiff.getRight();
- double top = rectWithDiff.getTop();
- cloudyEllipseImpl(left, bottom, right, top);
- finish();
- }
- /**
- * Returns the <code>BBox</code> entry (bounding box) for the appearance
- * stream form XObject.
- *
- * @return Bounding box for appearance stream form XObject.
- */
- public Rectangle getBBox() {
- return getRectangle();
- }
- /**
- * Returns the updated <code>Rect</code> entry for the annotation. The
- * rectangle completely contains the cloudy border.
- *
- * @return Annotation <code>Rect</code>.
- */
- public Rectangle getRectangle() {
- return new Rectangle((float) bboxMinX, (float) bboxMinY, (float) bboxMaxX, (float) bboxMaxY);
- }
- /**
- * Returns the <code>Matrix</code> entry for the appearance stream form
- * XObject.
- *
- * @return Matrix for appearance stream form XObject.
- */
- public AffineTransform getMatrix() {
- return AffineTransform.getTranslateInstance(-bboxMinX, -bboxMinY);
- }
- /**
- * Returns the updated <code>RD</code> entry for Square and Circle
- * annotations.
- *
- * @return Annotation <code>RD</code> value.
- */
- public Rectangle getRectDifference() {
- if (annotRect == null) {
- float d = (float) lineWidth / 2;
- return new Rectangle(d, d, (float) lineWidth, (float) lineWidth);
- }
- Rectangle re = (rectWithDiff != null) ? rectWithDiff : annotRect;
- float left = re.getLeft() - (float) bboxMinX;
- float bottom = re.getBottom() - (float) bboxMinY;
- float right = (float) bboxMaxX - re.getRight();
- float top = (float) bboxMaxY - re.getTop();
- return new Rectangle(left, bottom, right, top);
- }
- private static double cosine(double dx, double hypot) {
- if (Double.compare(hypot, 0.0) == 0) {
- return 0;
- }
- return dx / hypot;
- }
- private static double sine(double dy, double hypot) {
- if (Double.compare(hypot, 0.0) == 0) {
- return 0;
- }
- return dy / hypot;
- }
- /**
- * Cloudy rectangle implementation is based on converting the rectangle to a
- * polygon.
- */
- private void cloudyRectangleImpl(double left, double bottom, double right, double top, boolean isEllipse) throws IOException {
- double w = right - left, h = top - bottom;
- if (intensity <= 0.0) {
- cs.rectangle(left, bottom, w, h);
- bboxMinX = left;
- bboxMinY = bottom;
- bboxMaxX = right;
- bboxMaxY = top;
- return;
- }
- // Make a polygon with direction equal to the positive angle direction.
- Point2D.Double[] polygon;
- if (w < 1.0) {
- polygon = new Point2D.Double[] {
- new Point2D.Double(left, bottom), new Point2D.Double(left, top),
- new Point2D.Double(left, bottom)
- };
- }
- else if (h < 1.0) {
- polygon = new Point2D.Double[] {
- new Point2D.Double(left, bottom), new Point2D.Double(right, bottom),
- new Point2D.Double(left, bottom)
- };
- }
- else {
- polygon = new Point2D.Double[] {
- new Point2D.Double(left, bottom), new Point2D.Double(right, bottom),
- new Point2D.Double(right, top), new Point2D.Double(left, top),
- new Point2D.Double(left, bottom)
- };
- }
- cloudyPolygonImpl(polygon, isEllipse);
- }
- /**
- * Cloudy polygon implementation.
- *
- * @param vertices polygon vertices; first and last point must be equal
- * @param isEllipse specifies if the polygon represents an ellipse
- */
- private void cloudyPolygonImpl(Point2D.Double[] vertices, boolean isEllipse) throws IOException {
- Point2D.Double[] polygon = removeZeroLengthSegments(vertices);
- getPositivePolygon(polygon);
- int numPoints = polygon.length;
- if (numPoints < 2) {
- return;
- }
- if (intensity <= 0.0) {
- moveTo(polygon[0]);
- for (int i=1; i<numPoints; i++) {
- lineTo(polygon[i]);
- }
- return;
- }
- double cloudRadius = isEllipse ? getEllipseCloudRadius() : getPolygonCloudRadius();
- if (cloudRadius < 0.5) {
- cloudRadius = 0.5;
- }
- final double k = Math.cos(ANGLE_34_DEG);
- final double advIntermDefault = 2 * k * cloudRadius;
- final double advCornerDefault = k * cloudRadius;
- double[] array = new double[2];
- double anglePrev = 0;
- // The number of curls per polygon segment is hardly ever an integer,
- // so the length of some curls must be adjustable. We adjust the angle
- // of the trailing arc of corner curls and the leading arc of the first
- // intermediate curl.
- // In each polygon segment, we have n intermediate curls plus one half of a
- // corner curl at each end. One of the n intermediate curls is adjustable.
- // Thus the number of fixed (or unadjusted) intermediate curls is n - 1.
- // Find the adjusted angle `alpha` for the first corner curl.
- int n0 = computeParamsPolygon(advIntermDefault, advCornerDefault, k, cloudRadius, polygon[numPoints - 2].distance(polygon[0]), array);
- double alphaPrev = (n0 == 0) ? array[0] : ANGLE_34_DEG;
- for (int j=0; j+1<numPoints; j++) {
- Point2D.Double pt = polygon[j];
- Point2D.Double ptNext = polygon[j + 1];
- double length = pt.distance(ptNext);
- if (Double.compare(length, 0.0) == 0) {
- alphaPrev = ANGLE_34_DEG;
- continue;
- }
- // n is the number of intermediate curls in the current polygon segment.
- int n = computeParamsPolygon(advIntermDefault, advCornerDefault, k, cloudRadius, length, array);
- if (n < 0) {
- if (!outputStarted) {
- moveTo(pt);
- }
- continue;
- }
- double alpha = array[0], dx = array[1];
- double angleCur = Math.atan2(ptNext.y - pt.y, ptNext.x - pt.x);
- if (j == 0) {
- Point2D.Double ptPrev = polygon[numPoints - 2];
- anglePrev = Math.atan2(pt.y - ptPrev.y, pt.x - ptPrev.x);
- }
- double cos = cosine(ptNext.x - pt.x, length);
- double sin = sine(ptNext.y - pt.y, length);
- double x = pt.x;
- double y = pt.y;
- addCornerCurl(anglePrev, angleCur, cloudRadius, pt.x, pt.y, alpha, alphaPrev, !outputStarted);
- // Proceed to the center point of the first intermediate curl.
- double adv = 2 * k * cloudRadius + 2 * dx;
- x += adv * cos;
- y += adv * sin;
- // Create the first intermediate curl.
- int numInterm = n;
- if (n >= 1) {
- addFirstIntermediateCurl(angleCur, cloudRadius, alpha, x, y);
- x += advIntermDefault * cos;
- y += advIntermDefault * sin;
- numInterm = n - 1;
- }
- // Create one intermediate curl and replicate it along the polygon segment.
- Point2D.Double[] template = getIntermediateCurlTemplate(angleCur, cloudRadius);
- for (int i=0; i<numInterm; i++) {
- outputCurlTemplate(template, x, y);
- x += advIntermDefault * cos;
- y += advIntermDefault * sin;
- }
- anglePrev = angleCur;
- alphaPrev = (n == 0) ? alpha : ANGLE_34_DEG;
- }
- }
- /**
- * Computes parameters for a cloudy polygon: n, alpha, and dx.
- */
- private int computeParamsPolygon(double advInterm, double advCorner, double k, double r, double length, double[] array) {
- if (Double.compare(length, 0.0) == 0) {
- array[0] = ANGLE_34_DEG;
- array[1] = 0;
- return -1;
- }
- // n is the number of intermediate curls in the current polygon segment
- int n = (int) Math.ceil((length - 2 * advCorner) / advInterm);
- // Fitting error along polygon segment
- double e = length - (2 * advCorner + n * advInterm);
- // Fitting error per each adjustable half curl
- double dx = e / 2;
- // Convert fitting error to an angle that can be used to control arcs.
- double arg = (k * r + dx) / r;
- double alpha = (arg < -1.0 || arg > 1.0) ? 0.0 : Math.acos(arg);
- array[0] = alpha;
- array[1] = dx;
- return n;
- }
- /**
- * Creates a corner curl for polygons and ellipses.
- */
- private void addCornerCurl(double anglePrev, double angleCur, double radius, double cx, double cy, double alpha, double alphaPrev, boolean addMoveTo) throws IOException {
- double a = anglePrev + ANGLE_180_DEG + alphaPrev;
- double b = anglePrev + ANGLE_180_DEG + alphaPrev - Math.toRadians(22);
- getArcSegment(a, b, cx, cy, radius, radius, null, addMoveTo);
- a = b;
- b = angleCur - alpha;
- getArc(a, b, radius, radius, cx, cy, null, false);
- }
- /**
- * Generates the first intermediate curl for a cloudy polygon.
- */
- private void addFirstIntermediateCurl(double angleCur, double r, double alpha, double cx, double cy) throws IOException {
- final double D = Math.toRadians(30);
- double a = angleCur + ANGLE_180_DEG;
- getArcSegment(a + alpha, a + alpha - D, cx, cy, r, r, null, false);
- getArcSegment(a + alpha - D, a + ANGLE_90_DEG, cx, cy, r, r, null, false);
- getArcSegment(a + ANGLE_90_DEG, a + ANGLE_180_DEG - ANGLE_34_DEG, cx, cy, r, r, null, false);
- }
- /**
- * Returns a template for intermediate curls in a cloudy polygon.
- */
- private Point2D.Double[] getIntermediateCurlTemplate(double angleCur, double r) throws IOException {
- ArrayList<Point2D.Double> points = new ArrayList<>();
- double a = angleCur + ANGLE_180_DEG;
- getArcSegment(a + ANGLE_34_DEG, a + ANGLE_12_DEG, 0, 0, r, r, points, false);
- getArcSegment(a + ANGLE_12_DEG, a + ANGLE_90_DEG, 0, 0, r, r, points, false);
- getArcSegment(a + ANGLE_90_DEG, a + ANGLE_180_DEG - ANGLE_34_DEG, 0, 0, r, r, points, false);
- return points.toArray(new Point2D.Double[points.size()]);
- }
- /**
- * Writes the curl template points to the output and applies translation (x, y).
- */
- private void outputCurlTemplate(Point2D.Double[] template, double x, double y) throws IOException {
- int n = template.length, i = 0;
- if ((n % 3) == 1) {
- Point2D.Double a = template[0];
- moveTo(a.x + x, a.y + y);
- i++;
- }
- for (; i+2<n; i+=3) {
- Point2D.Double a = template[i];
- Point2D.Double b = template[i + 1];
- Point2D.Double c = template[i + 2];
- curveTo(a.x + x, a.y + y, b.x + x, b.y + y, c.x + x, c.y + y);
- }
- }
- private Rectangle applyRectDiff(Rectangle rd, double min) {
- float rectLeft = annotRect.getLeft();
- float rectBottom = annotRect.getBottom();
- float rectRight = annotRect.getRight();
- float rectTop = annotRect.getTop();
- // Normalize
- rectLeft = Math.min(rectLeft, rectRight);
- rectBottom = Math.min(rectBottom, rectTop);
- rectRight = Math.max(rectLeft, rectRight);
- rectTop = Math.max(rectBottom, rectTop);
- double rdLeft, rdBottom, rdRight, rdTop;
- if (rd != null) {
- rdLeft = Math.max(rd.getLeft(), min);
- rdBottom = Math.max(rd.getBottom(), min);
- rdRight = Math.max(rd.getRight(), min);
- rdTop = Math.max(rd.getTop(), min);
- }
- else {
- rdLeft = min;
- rdBottom = min;
- rdRight = min;
- rdTop = min;
- }
- rectLeft += rdLeft;
- rectBottom += rdBottom;
- rectRight -= rdRight;
- rectTop -= rdTop;
- // here try to check
- return new Rectangle(rectLeft, rectBottom, rectRight, rectTop);
- }
- private void reversePolygon(Point2D.Double[] points) {
- int len = points.length;
- int n = len / 2;
- for (int i=0; i<n; i++) {
- int j = len - i - 1;
- Point2D.Double pi = points[i];
- Point2D.Double pj = points[j];
- points[i] = pj;
- points[j] = pi;
- }
- }
- /**
- * Makes a polygon whose direction is the same as the positive angle
- * direction in the coordinate system. The polygon must not intersect
- * itself.
- */
- private void getPositivePolygon(Point2D.Double[] points) {
- if (getPolygonDirection(points) < 0) {
- reversePolygon(points);
- }
- }
- /**
- * Returns the direction of the specified polygon. A positive value
- * indicates that the polygon's direction is the same as the direction of
- * positive angles in the coordinate system. A negative value indicates the
- * opposite direction.
- *
- * The polygon must not intersect itself. A 2-point polygon is not
- * acceptable. This is based on the "shoelace formula".
- */
- private double getPolygonDirection(Point2D.Double[] points) {
- double a = 0;
- int len = points.length;
- for (int i=0; i<len; i++) {
- int j = (i + 1) % len;
- a += points[i].x * points[j].y - points[i].y * points[j].x;
- }
- return a;
- }
- /**
- * Creates one or more Bezier curves that represent an elliptical arc.
- * Angles are in radians. The arc will always proceed in the positive angle
- * direction. If the argument `out` is null, this writes the results to the
- * instance variable `output`.
- */
- private void getArc(double startAng, double endAng, double rx, double ry, double cx, double cy, ArrayList<Point2D.Double> out, boolean addMoveTo) throws IOException {
- final double angleIncr = Math.PI / 2;
- double startx = rx * Math.cos(startAng) + cx;
- double starty = ry * Math.sin(startAng) + cy;
- double angleTodo = endAng - startAng;
- while (angleTodo < 0) {
- angleTodo += 2 * Math.PI;
- }
- double sweep = angleTodo, angleDone = 0;
- if (addMoveTo) {
- if (out != null) {
- out.add(new Point2D.Double(startx, starty));
- }
- else {
- moveTo(startx, starty);
- }
- }
- while (angleTodo > angleIncr) {
- getArcSegment(startAng + angleDone,
- startAng + angleDone + angleIncr, cx, cy, rx, ry, out, false);
- angleDone += angleIncr;
- angleTodo -= angleIncr;
- }
- if (angleTodo > 0) {
- getArcSegment(startAng + angleDone, startAng + sweep, cx, cy, rx, ry, out, false);
- }
- }
- /**
- * Creates a single Bezier curve that represents a section of an elliptical
- * arc. The sweep angle of the section must not be larger than 90 degrees.
- * If argument `out` is null, this writes the results to the instance
- * variable `output`.
- */
- private void getArcSegment(double startAng, double endAng, double cx, double cy, double rx, double ry, ArrayList<Point2D.Double> out, boolean addMoveTo) throws IOException {
- // Algorithm is from the FAQ of the news group comp.text.pdf
- double cos_a = Math.cos(startAng);
- double sin_a = Math.sin(startAng);
- double cos_b = Math.cos(endAng);
- double sin_b = Math.sin(endAng);
- double denom = Math.sin((endAng - startAng) / 2.0);
- if (Double.compare(denom, 0.0) == 0) {
- // This can happen only if endAng == startAng.
- // The arc sweep angle is zero, so we create no arc at all.
- if (addMoveTo) {
- double xs = cx + rx * cos_a;
- double ys = cy + ry * sin_a;
- if (out != null) {
- out.add(new Point2D.Double(xs, ys));
- }
- else {
- moveTo(xs, ys);
- }
- }
- return;
- }
- double bcp = 1.333333333 * (1 - Math.cos((endAng - startAng) / 2.0)) / denom;
- double p1x = cx + rx * (cos_a - bcp * sin_a);
- double p1y = cy + ry * (sin_a + bcp * cos_a);
- double p2x = cx + rx * (cos_b + bcp * sin_b);
- double p2y = cy + ry * (sin_b - bcp * cos_b);
- double p3x = cx + rx * cos_b;
- double p3y = cy + ry * sin_b;
- if (addMoveTo) {
- double xs = cx + rx * cos_a;
- double ys = cy + ry * sin_a;
- if (out != null) {
- out.add(new Point2D.Double(xs, ys));
- }
- else {
- moveTo(xs, ys);
- }
- }
- if (out != null) {
- out.add(new Point2D.Double(p1x, p1y));
- out.add(new Point2D.Double(p2x, p2y));
- out.add(new Point2D.Double(p3x, p3y));
- }
- else {
- curveTo(p1x, p1y, p2x, p2y, p3x, p3y);
- }
- }
- /**
- * Flattens an ellipse into a polygon.
- */
- private static Point2D.Double[] flattenEllipse(double left, double bottom, double right, double top) {
- Ellipse2D.Double ellipse = new Ellipse2D.Double(left, bottom, right - left, top - bottom);
- final double flatness = 0.50;
- PathIterator iterator = ellipse.getPathIterator(null, flatness);
- double[] coords = new double[6];
- ArrayList<Point2D.Double> points = new ArrayList<>();
- while (!iterator.isDone()) {
- switch (iterator.currentSegment(coords)) {
- case PathIterator.SEG_MOVETO:
- case PathIterator.SEG_LINETO:
- points.add(new Point2D.Double(coords[0], coords[1]));
- break;
- // Curve segments are not expected because the path iterator is
- // flattened. SEG_CLOSE can be ignored.
- }
- iterator.next();
- }
- int size = points.size();
- final double closeTestLimit = 0.05;
- if (size >= 2 && points.get(size - 1).distance(points.get(0)) > closeTestLimit) {
- points.add(points.get(points.size() - 1));
- }
- return points.toArray(new Point2D.Double[points.size()]);
- }
- /**
- * Cloudy ellipse implementation.
- */
- private void cloudyEllipseImpl(final double leftOrig, final double bottomOrig, final double rightOrig, final double topOrig) throws IOException {
- if (intensity <= 0.0) {
- drawBasicEllipse(leftOrig, bottomOrig, rightOrig, topOrig);
- return;
- }
- double left = leftOrig, bottom = bottomOrig, right = rightOrig, top = topOrig;
- double width = right - left, height = top - bottom;
- double cloudRadius = getEllipseCloudRadius();
- // Omit cloudy border if the ellipse is very small.
- final double threshold1 = 0.50 * cloudRadius;
- if (width < threshold1 && height < threshold1) {
- drawBasicEllipse(left, bottom, right, top);
- return;
- }
- // Draw a cloudy rectangle instead of an ellipse when the
- // width or height is very small.
- final double threshold2 = 5;
- if ((width < threshold2 && height > 20) || (width > 20 && height < threshold2)) {
- cloudyRectangleImpl(left, bottom, right, top, true);
- return;
- }
- // Decrease radii (while center point does not move). This makes the
- // "tails" of the curls almost touch the ellipse outline.
- double radiusAdj = Math.sin(ANGLE_12_DEG) * cloudRadius - 1.50;
- if (width > 2 * radiusAdj) {
- left += radiusAdj;
- right -= radiusAdj;
- }
- else {
- double mid = (left + right) / 2;
- left = mid - 0.10;
- right = mid + 0.10;
- }
- if (height > 2 * radiusAdj) {
- top -= radiusAdj;
- bottom += radiusAdj;
- }
- else {
- double mid = (top + bottom) / 2;
- top = mid + 0.10;
- bottom = mid - 0.10;
- }
- // Flatten the ellipse into a polygon. The segment lengths of the flattened
- // result don't need to be extremely short because the loop below is able to
- // interpolate between polygon points when it computes the center points
- // at which each curl is placed.
- Point2D.Double[] flatPolygon = flattenEllipse(left, bottom, right, top);
- int numPoints = flatPolygon.length;
- if (numPoints < 2) {
- return;
- }
- double totLen = 0;
- for (int i=1; i<numPoints; i++) {
- totLen += flatPolygon[i - 1].distance(flatPolygon[i]);
- }
- final double k = Math.cos(ANGLE_34_DEG);
- double curlAdvance = 2 * k * cloudRadius;
- int n = (int) Math.ceil(totLen / curlAdvance);
- if (n < 2) {
- drawBasicEllipse(leftOrig, bottomOrig, rightOrig, topOrig);
- return;
- }
- curlAdvance = totLen / n;
- cloudRadius = curlAdvance / (2 * k);
- if (cloudRadius < 0.5) {
- cloudRadius = 0.5;
- curlAdvance = 2 * k * cloudRadius;
- }
- else if (cloudRadius < 3.0) {
- // Draw a small circle when the scaled radius becomes very small.
- // This happens also if intensity is much smaller than 1.
- drawBasicEllipse(leftOrig, bottomOrig, rightOrig, topOrig);
- return;
- }
- // Construct centerPoints array, in which each point is the center point of a curl.
- // The length of each centerPoints segment ideally equals curlAdv but that
- // is not true in regions where the ellipse curvature is high.
- int centerPointsLength = n;
- Point2D.Double[] centerPoints = new Point2D.Double[centerPointsLength];
- int centerPointsIndex = 0;
- double lengthRemain = 0;
- final double comparisonToler = lineWidth * 0.10;
- for (int i=0; i+1<numPoints; i++) {
- Point2D.Double p1 = flatPolygon[i];
- Point2D.Double p2 = flatPolygon[i + 1];
- double dx = p2.x - p1.x, dy = p2.y - p1.y;
- double length = p1.distance(p2);
- if (Double.compare(length, 0.0) == 0) {
- continue;
- }
- double lengthTodo = length + lengthRemain;
- if (lengthTodo >= curlAdvance - comparisonToler || i == numPoints - 2) {
- double cos = cosine(dx, length), sin = sine(dy, length);
- double d = curlAdvance - lengthRemain;
- do {
- double x = p1.x + d * cos;
- double y = p1.y + d * sin;
- if (centerPointsIndex < centerPointsLength) {
- centerPoints[centerPointsIndex++] = new Point2D.Double(x, y);
- }
- lengthTodo -= curlAdvance;
- d += curlAdvance;
- } while (lengthTodo >= curlAdvance - comparisonToler);
- lengthRemain = lengthTodo;
- if (lengthRemain < 0) {
- lengthRemain = 0;
- }
- }
- else {
- lengthRemain += length;
- }
- }
- // Note: centerPoints does not repeat the first point as the last point
- // to create a "closing" segment.
- // Place a curl at each point of the centerPoints array.
- // In regions where the ellipse curvature is high, the centerPoints segments
- // are shorter than the actual distance along the ellipse. Thus we must
- // again compute arc adjustments like in cloudy polygons.
- numPoints = centerPointsIndex;
- double anglePrev = 0, alphaPrev = 0;
- for (int i=0; i<numPoints; i++) {
- int idxNext = i + 1;
- if (i + 1 >= numPoints) {
- idxNext = 0;
- }
- Point2D.Double pt = centerPoints[i];
- Point2D.Double ptNext = centerPoints[idxNext];
- if (i == 0) {
- Point2D.Double ptPrev = centerPoints[numPoints - 1];
- anglePrev = Math.atan2(pt.y - ptPrev.y, pt.x - ptPrev.x);
- alphaPrev = computeParamsEllipse(ptPrev, pt, cloudRadius, curlAdvance);
- }
- double angleCur = Math.atan2(ptNext.y - pt.y, ptNext.x - pt.x);
- double alpha = computeParamsEllipse(pt, ptNext, cloudRadius, curlAdvance);
- addCornerCurl(anglePrev, angleCur, cloudRadius, pt.x, pt.y, alpha, alphaPrev, !outputStarted);
- anglePrev = angleCur;
- alphaPrev = alpha;
- }
- }
- /**
- * Computes the alpha parameter for an ellipse curl.
- */
- private double computeParamsEllipse(Point2D.Double pt, Point2D.Double ptNext, double r, double curlAdv) {
- double length = pt.distance(ptNext);
- if (Double.compare(length, 0.0) == 0) {
- return ANGLE_34_DEG;
- }
- double e = length - curlAdv;
- double arg = (curlAdv / 2 + e / 2) / r;
- return (arg < -1.0 || arg > 1.0) ? 0.0 : Math.acos(arg);
- }
- private Point2D.Double[] removeZeroLengthSegments(Point2D.Double[] polygon) {
- int np = polygon.length;
- if (np <= 2) {
- return polygon;
- }
- final double toler = 0.50;
- int npNew = np;
- Point2D.Double ptPrev = polygon[0];
- // Don't remove the last point if it equals the first point.
- for (int i=1; i<np; i++) {
- Point2D.Double pt = polygon[i];
- if (Math.abs(pt.x - ptPrev.x) < toler && Math.abs(pt.y - ptPrev.y) < toler) {
- polygon[i] = null;
- npNew--;
- }
- ptPrev = pt;
- }
- if (npNew == np) {
- return polygon;
- }
- Point2D.Double[] polygonNew = new Point2D.Double[npNew];
- int j = 0;
- for (int i=0; i<np; i++) {
- Point2D.Double pt = polygon[i];
- if (pt != null) {
- polygonNew[j++] = pt;
- }
- }
- return polygonNew;
- }
- /**
- * Draws an ellipse without a cloudy border effect.
- */
- private void drawBasicEllipse(double left, double bottom, double right, double top) throws IOException {
- double rx = Math.abs(right - left) / 2;
- double ry = Math.abs(top - bottom) / 2;
- double cx = (left + right) / 2;
- double cy = (bottom + top) / 2;
- getArc(0, 2 * Math.PI, rx, ry, cx, cy, null, true);
- }
- private void beginOutput(double x, double y) throws IOException {
- bboxMinX = x;
- bboxMinY = y;
- bboxMaxX = x;
- bboxMaxY = y;
- outputStarted = true;
- // Set line join to bevel to avoid spikes
- cs.setLineJoin(PdfAppearance.LINE_JOIN_BEVEL);
- }
- private void updateBBox(double x, double y) {
- bboxMinX = Math.min(bboxMinX, x);
- bboxMinY = Math.min(bboxMinY, y);
- bboxMaxX = Math.max(bboxMaxX, x);
- bboxMaxY = Math.max(bboxMaxY, y);
- }
- private void moveTo(Point2D.Double p) throws IOException {
- moveTo(p.x, p.y);
- }
- private void moveTo(double x, double y) throws IOException {
- if (outputStarted) {
- updateBBox(x, y);
- }
- else {
- beginOutput(x, y);
- }
- cs.moveTo(x, y);
- }
- private void lineTo(Point2D.Double p) throws IOException {
- lineTo(p.x, p.y);
- }
- private void lineTo(double x, double y) throws IOException {
- if (outputStarted) {
- updateBBox(x, y);
- }
- else {
- beginOutput(x, y);
- }
- cs.lineTo(x, y);
- }
- private void curveTo(Point2D.Double a, Point2D.Double b, Point2D.Double c) throws IOException {
- curveTo(a.x, a.y, b.x, b.y, c.x, c.y);
- }
- private void curveTo(double ax, double ay, double bx, double by, double cx, double cy) throws IOException {
- updateBBox(ax, ay);
- updateBBox(bx, by);
- updateBBox(cx, cy);
- cs.curveTo(ax, ay, bx, by, cx, cy);
- }
- private void finish() throws IOException {
- if (outputStarted) {
- cs.closePath();
- }
- if (lineWidth > 0) {
- double d = lineWidth / 2;
- bboxMinX -= d;
- bboxMinY -= d;
- bboxMaxX += d;
- bboxMaxY += d;
- }
- }
- private double getEllipseCloudRadius() {
- // Equation deduced from Acrobat Reader's appearance streams. Circle
- // annotations have a slightly larger radius than Polygons and Squares.
- return 4.75 * intensity + 0.5 * lineWidth;
- }
- private double getPolygonCloudRadius() {
- // Equation deduced from Acrobat Reader's appearance streams.
- return 4 * intensity + 0.5 * lineWidth;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement