in pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/CloudyBorder.java [743:935]
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;
double bottom = bottomOrig;
double right = rightOrig;
double top = topOrig;
double width = right - left;
double 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;
double 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);
double 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;
double 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;
}
}