You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ti...@apache.org on 2018/05/19 19:15:17 UTC
svn commit: r1831914 -
/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java
Author: tilman
Date: Sat May 19 19:15:17 2018
New Revision: 1831914
URL: http://svn.apache.org/viewvc?rev=1831914&view=rev
Log:
PDFBOX-3353: draw line endings
Modified:
pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java
Modified: pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java?rev=1831914&r1=1831913&r2=1831914&view=diff
==============================================================================
--- pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java (original)
+++ pdfbox/trunk/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java Sat May 19 19:15:17 2018
@@ -28,7 +28,12 @@ import org.apache.pdfbox.pdmodel.graphic
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationPolyline;
import org.apache.pdfbox.pdmodel.PDAppearanceContentStream;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLine;
+import static org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLine.LE_NONE;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
+import static org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDAbstractAppearanceHandler.INTERIOR_COLOR_STYLES;
+import static org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDLineAppearanceHandler.ARROW_ANGLE;
+import org.apache.pdfbox.util.Matrix;
/**
* Handler to generate the polyline annotations appearance.
@@ -84,17 +89,20 @@ public class PDPolylineAppearanceHandler
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
- rect.setLowerLeftX(Math.min(minX - ab.width / 2, rect.getLowerLeftX()));
- rect.setLowerLeftY(Math.min(minY - ab.width / 2, rect.getLowerLeftY()));
- rect.setUpperRightX(Math.max(maxX + ab.width, rect.getUpperRightX()));
- rect.setUpperRightY(Math.max(maxY + ab.width, rect.getUpperRightY()));
+ // arrow length is 9 * width at about 30° => 10 * width seems to be enough
+ rect.setLowerLeftX(Math.min(minX - ab.width * 10, rect.getLowerLeftX()));
+ rect.setLowerLeftY(Math.min(minY - ab.width * 10, rect.getLowerLeftY()));
+ rect.setUpperRightX(Math.max(maxX + ab.width * 10, rect.getUpperRightX()));
+ rect.setUpperRightY(Math.max(maxY + ab.width * 10, rect.getUpperRightY()));
annotation.setRectangle(rect);
try
{
try (PDAppearanceContentStream cs = getNormalAppearanceAsContentStream())
{
+ boolean hasBackground = cs.setNonStrokingColorOnDemand(annotation.getInteriorColor());
setOpacity(cs, annotation.getConstantOpacity());
+ boolean hasStroke = cs.setStrokingColorOnDemand(color);
cs.setStrokingColor(color);
if (ab.dashArray != null)
@@ -112,10 +120,9 @@ public class PDPolylineAppearanceHandler
if (SHORT_STYLES.contains(annotation.getStartPointEndingStyle()))
{
// modify coordinate to shorten the segment
+ // https://stackoverflow.com/questions/7740507/extend-a-line-segment-a-specific-distance
float x1 = pathsArray[2];
float y1 = pathsArray[3];
-
- // https://stackoverflow.com/questions/7740507/extend-a-line-segment-a-specific-distance
float len = (float) (Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2)));
if (Float.compare(len, 0) != 0)
{
@@ -125,15 +132,15 @@ public class PDPolylineAppearanceHandler
}
cs.moveTo(x, y);
}
- else if (i == pathsArray.length / 2 - 1)
+ else
{
- if (SHORT_STYLES.contains(annotation.getEndPointEndingStyle()))
+ if (i == pathsArray.length / 2 - 1 &&
+ SHORT_STYLES.contains(annotation.getEndPointEndingStyle()))
{
// modify coordinate to shorten the segment
+ // https://stackoverflow.com/questions/7740507/extend-a-line-segment-a-specific-distance
float x0 = pathsArray[pathsArray.length - 4];
float y0 = pathsArray[pathsArray.length - 3];
-
- // https://stackoverflow.com/questions/7740507/extend-a-line-segment-a-specific-distance
float len = (float) (Math.sqrt(Math.pow(x0 - x, 2) + Math.pow(y0 - y, 2)));
if (Float.compare(len, 0) != 0)
{
@@ -143,19 +150,41 @@ public class PDPolylineAppearanceHandler
}
cs.lineTo(x, y);
}
- else
- {
- cs.lineTo(x, y);
- }
}
- //TODO line endings (LE) are missing
- // How it could be done by reusing some of the code from the line handler
- // 1) do a transform so that first and last "arms" are imagined flat
- // (like in line handler)
- // 2) refactor + reuse the line handler code that draws the ending shapes
+ cs.stroke();
+
+ // do a transform so that first and last "arms" are imagined flat, like in line handler
// the alternative would be to apply the transform to the LE shapes directly,
// which would be more work and produce code difficult to understand
- cs.stroke();
+
+ // this must be done after polyline draw, to avoid line crossing a filled shape
+ if (!LE_NONE.equals(annotation.getStartPointEndingStyle()))
+ {
+ // check only needed to avoid q cm Q if LE_NONE
+ float x2 = pathsArray[2];
+ float y2 = pathsArray[3];
+ float x1 = pathsArray[0];
+ float y1 = pathsArray[1];
+ cs.saveGraphicsState();
+ double angle = Math.atan2(y2 - y1, x2 - x1);
+ cs.transform(Matrix.getRotateInstance(angle, x1, y1));
+ drawStyle(annotation.getStartPointEndingStyle(), cs, 0, 0, ab.width, hasStroke, hasBackground);
+ cs.restoreGraphicsState();
+ }
+
+ if (!LE_NONE.equals(annotation.getEndPointEndingStyle()))
+ {
+ // check only needed to avoid q cm Q if LE_NONE
+ float x1 = pathsArray[pathsArray.length - 4];
+ float y1 = pathsArray[pathsArray.length - 3];
+ float x2 = pathsArray[pathsArray.length - 2];
+ float y2 = pathsArray[pathsArray.length - 1];
+ // save / restore not needed because it's the last one
+ double angle = Math.atan2(y2 - y1, x2 - x1);
+ cs.transform(Matrix.getRotateInstance(angle, x1, y1));
+ float lineLength = (float) Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
+ drawStyle(annotation.getEndPointEndingStyle(), cs, lineLength, 0, ab.width, hasStroke, hasBackground);
+ }
}
}
catch (IOException ex)
@@ -218,4 +247,142 @@ public class PDPolylineAppearanceHandler
return 1;
}
+
+ //TODO refactor to remove double code drawStyle and related
+ // (100% copied from line handler)
+
+ private void drawStyle(String style, final PDAppearanceContentStream cs, float x, float y,
+ float width, boolean hasStroke, boolean hasBackground) throws IOException
+ {
+ switch (style)
+ {
+ case PDAnnotationLine.LE_OPEN_ARROW:
+ case PDAnnotationLine.LE_CLOSED_ARROW:
+ if (Float.compare(x, 0) != 0)
+ {
+ // ending
+ drawArrow(cs, x - width, y, -width * 9);
+ }
+ else
+ {
+ // start
+ drawArrow(cs, width, y, width * 9);
+ }
+ if (PDAnnotationLine.LE_CLOSED_ARROW.equals(style))
+ {
+ cs.closePath();
+ }
+ break;
+ case PDAnnotationLine.LE_BUTT:
+ cs.moveTo(x, y - width * 3);
+ cs.lineTo(x, y + width * 3);
+ break;
+ case PDAnnotationLine.LE_DIAMOND:
+ drawDiamond(cs, x, y, width * 3);
+ break;
+ case PDAnnotationLine.LE_SQUARE:
+ cs.addRect(x - width * 3, y - width * 3, width * 6, width * 6);
+ break;
+ case PDAnnotationLine.LE_CIRCLE:
+ addCircle(cs, x, y, width * 3);
+ break;
+ case PDAnnotationLine.LE_R_OPEN_ARROW:
+ case PDAnnotationLine.LE_R_CLOSED_ARROW:
+ if (Float.compare(x, 0) != 0)
+ {
+ // ending
+ drawArrow(cs, x + width, y, width * 9);
+ }
+ else
+ {
+ // start
+ drawArrow(cs, -width, y, -width * 9);
+ }
+ if (PDAnnotationLine.LE_R_CLOSED_ARROW.equals(style))
+ {
+ cs.closePath();
+ }
+ break;
+ case PDAnnotationLine.LE_SLASH:
+ // the line is 18 x linewidth at an angle of 60°
+ cs.moveTo(x + (float) (Math.cos(Math.toRadians(60)) * width * 9),
+ y + (float) (Math.sin(Math.toRadians(60)) * width * 9));
+ cs.lineTo(x + (float) (Math.cos(Math.toRadians(240)) * width * 9),
+ y + (float) (Math.sin(Math.toRadians(240)) * width * 9));
+ break;
+ default:
+ break;
+ }
+ if (INTERIOR_COLOR_STYLES.contains(style))
+ {
+ cs.drawShape(width, hasStroke, hasBackground);
+ }
+ else if (!PDAnnotationLine.LE_NONE.equals(style))
+ {
+ // need to do this separately, because sometimes /IC is set anyway
+ cs.drawShape(width, hasStroke, false);
+ }
+ }
+
+ /**
+ * Add the two arms of a horizontal arrow.
+ *
+ * @param cs Content stream
+ * @param x
+ * @param y
+ * @param len The arm length. Positive goes to the right, negative goes to the left.
+ *
+ * @throws IOException If the content stream could not be written
+ */
+ private void drawArrow(PDAppearanceContentStream cs, float x, float y, float len) throws IOException
+ {
+ // strategy for arrows: angle 30°, arrow arm length = 9 * line width
+ // cos(angle) = x position
+ // sin(angle) = y position
+ // this comes very close to what Adobe is doing
+ cs.moveTo(x + (float) (Math.cos(ARROW_ANGLE) * len), y + (float) (Math.sin(ARROW_ANGLE) * len));
+ cs.lineTo(x, y);
+ cs.lineTo(x + (float) (Math.cos(ARROW_ANGLE) * len), y - (float) (Math.sin(ARROW_ANGLE) * len));
+ }
+
+ /**
+ * Add a square diamond shape (corner on top) to the path.
+ *
+ * @param cs Content stream
+ * @param x
+ * @param y
+ * @param r Radius (to a corner)
+ *
+ * @throws IOException If the content stream could not be written
+ */
+ private void drawDiamond(PDAppearanceContentStream cs, float x, float y, float r) throws IOException
+ {
+ cs.moveTo(x - r, y);
+ cs.lineTo(x, y + r);
+ cs.lineTo(x + r, y);
+ cs.lineTo(x, y - r);
+ cs.closePath();
+ }
+
+ /**
+ * Add a circle shape to the path.
+ *
+ * @param cs Content stream
+ * @param x
+ * @param y
+ * @param r Radius
+ *
+ * @throws IOException If the content stream could not be written
+ */
+ private void addCircle(PDAppearanceContentStream cs, float x, float y, float r) throws IOException
+ {
+ // http://stackoverflow.com/a/2007782/535646
+ float magic = r * 0.551784f;
+ cs.moveTo(x, y + r);
+ cs.curveTo(x + magic, y + r, x + r, y + magic, x + r, y);
+ cs.curveTo(x + r, y - magic, x + magic, y - r, x, y - r);
+ cs.curveTo(x - magic, y - r, x - r, y - magic, x - r, y);
+ cs.curveTo(x - r, y + magic, x - magic, y + r, x, y + r);
+ cs.closePath();
+ }
}