From commits-return-14821-archive-asf-public=cust-asf.ponee.io@pdfbox.apache.org Sun Jun 16 16:02:14 2019 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [207.244.88.153]) by mx-eu-01.ponee.io (Postfix) with SMTP id 89350180662 for ; Sun, 16 Jun 2019 18:02:13 +0200 (CEST) Received: (qmail 59445 invoked by uid 500); 16 Jun 2019 16:02:12 -0000 Mailing-List: contact commits-help@pdfbox.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@pdfbox.apache.org Delivered-To: mailing list commits@pdfbox.apache.org Received: (qmail 59436 invoked by uid 99); 16 Jun 2019 16:02:12 -0000 Received: from Unknown (HELO svn01-us-west.apache.org) (209.188.14.144) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 16 Jun 2019 16:02:12 +0000 Received: from svn01-us-west.apache.org (localhost [127.0.0.1]) by svn01-us-west.apache.org (ASF Mail Server at svn01-us-west.apache.org) with ESMTP id A8A2A3A05C4 for ; Sun, 16 Jun 2019 16:02:11 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r1861466 - in /pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox: cos/ pdmodel/interactive/annotation/ pdmodel/interactive/annotation/handlers/ Date: Sun, 16 Jun 2019 16:02:11 -0000 To: commits@pdfbox.apache.org From: msahyoun@apache.org X-Mailer: svnmailer-1.0.9 Message-Id: <20190616160211.A8A2A3A05C4@svn01-us-west.apache.org> Author: msahyoun Date: Sun Jun 16 16:02:10 2019 New Revision: 1861466 URL: http://svn.apache.org/viewvc?rev=1861466&view=rev Log: PDFBOX-4574: add support for Polygon and Polyline appearance handler to Markup annotation Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolygonAppearanceHandler.java pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java?rev=1861466&r1=1861465&r2=1861466&view=diff ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java (original) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java Sun Jun 16 16:02:10 2019 @@ -432,6 +432,7 @@ public final class COSName extends COSBa public static final COSName PARENT = new COSName("Parent"); public static final COSName PARENT_TREE = new COSName("ParentTree"); public static final COSName PARENT_TREE_NEXT_KEY = new COSName("ParentTreeNextKey"); + public static final COSName PATH = new COSName("Path"); public static final COSName PATTERN = new COSName("Pattern"); public static final COSName PATTERN_TYPE = new COSName("PatternType"); public static final COSName PDF_DOC_ENCODING = new COSName("PDFDocEncoding"); Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java?rev=1861466&r1=1861465&r2=1861466&view=diff ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java (original) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/PDAnnotationMarkup.java Sun Jun 16 16:02:10 2019 @@ -18,11 +18,16 @@ package org.apache.pdfbox.pdmodel.intera import java.io.IOException; import java.util.Calendar; + +import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSStream; import org.apache.pdfbox.cos.COSString; +import org.apache.pdfbox.pdmodel.graphics.color.PDColor; +import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDAppearanceHandler; +import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDPolygonAppearanceHandler; /** * This class represents the additional fields of a Markup type Annotation. See section 12.5.6 of ISO32000-1:2008 @@ -32,6 +37,9 @@ import org.apache.pdfbox.cos.COSString; */ public class PDAnnotationMarkup extends PDAnnotation { + + private PDAppearanceHandler customAppearanceHandler; + /** * Constant for a FreeText type of annotation. */ @@ -354,4 +362,232 @@ public class PDAnnotationMarkup extends return null; } + // PDF 32000 specification has "the interior color with which to fill the annotation’s line endings" + // but it is the inside of the polygon. + + /** + * This will set interior color. + * + * @param ic color. + */ + public void setInteriorColor(PDColor ic) + { + getCOSObject().setItem(COSName.IC, ic.toCOSArray()); + } + + /** + * This will retrieve the interior color. + * + * @return object representing the color. + */ + public PDColor getInteriorColor() + { + return getColor(COSName.IC); + } + + /** + * This will set the border effect dictionary, specifying effects to be applied when drawing the + * line. This is supported by PDF 1.5 and higher. + * + * @param be The border effect dictionary to set. + * + */ + public void setBorderEffect(PDBorderEffectDictionary be) + { + getCOSObject().setItem(COSName.BE, be); + } + + /** + * This will retrieve the border effect dictionary, specifying effects to be applied used in + * drawing the line. + * + * @return The border effect dictionary + */ + public PDBorderEffectDictionary getBorderEffect() + { + COSDictionary be = (COSDictionary) getCOSObject().getDictionaryObject(COSName.BE); + if (be != null) + { + return new PDBorderEffectDictionary(be); + } + else + { + return null; + } + } + + /** + * This will set the line ending style for the start point, see the LE_ constants for the possible values. + * + * @param style The new style. + */ + public void setStartPointEndingStyle(String style) + { + String actualStyle = style == null ? PDAnnotationLine.LE_NONE : style; + COSBase base = getCOSObject().getDictionaryObject(COSName.LE); + COSArray array; + if (!(base instanceof COSArray) || ((COSArray) base).size() == 0) + { + array = new COSArray(); + array.add(COSName.getPDFName(actualStyle)); + array.add(COSName.getPDFName(PDAnnotationLine.LE_NONE)); + getCOSObject().setItem(COSName.LE, array); + } + else + { + array = (COSArray) base; + array.setName(0, actualStyle); + } + } + + /** + * This will retrieve the line ending style for the start point, possible values shown in the LE_ constants section. + * + * @return The ending style for the start point, LE_NONE if missing, never null. + */ + public String getStartPointEndingStyle() + { + COSBase base = getCOSObject().getDictionaryObject(COSName.LE); + if (base instanceof COSArray && ((COSArray) base).size() >= 2) + { + return ((COSArray) base).getName(0, PDAnnotationLine.LE_NONE); + } + return PDAnnotationLine.LE_NONE; + } + + /** + * This will set the line ending style for the end point, see the LE_ constants for the possible values. + * + * @param style The new style. + */ + public void setEndPointEndingStyle(String style) + { + String actualStyle = style == null ? PDAnnotationLine.LE_NONE : style; + COSBase base = getCOSObject().getDictionaryObject(COSName.LE); + COSArray array; + if (!(base instanceof COSArray) || ((COSArray) base).size() < 2) + { + array = new COSArray(); + array.add(COSName.getPDFName(PDAnnotationLine.LE_NONE)); + array.add(COSName.getPDFName(actualStyle)); + getCOSObject().setItem(COSName.LE, array); + } + else + { + array = (COSArray) base; + array.setName(1, actualStyle); + } + } + + /** + * This will retrieve the line ending style for the end point, possible values shown in the LE_ constants section. + * + * @return The ending style for the end point, LE_NONE if missing, never null. + */ + public String getEndPointEndingStyle() + { + COSBase base = getCOSObject().getDictionaryObject(COSName.LE); + if (base instanceof COSArray && ((COSArray) base).size() >= 2) + { + return ((COSArray) base).getName(1, PDAnnotationLine.LE_NONE); + } + return PDAnnotationLine.LE_NONE; + } + + + /** + * This will retrieve the numbers that shall represent the alternating horizontal and vertical + * coordinates. + * + * @return An array of floats representing the alternating horizontal and vertical coordinates. + */ + public float[] getVertices() + { + COSBase base = getCOSObject().getDictionaryObject(COSName.VERTICES); + if (base instanceof COSArray) + { + return ((COSArray) base).toFloatArray(); + } + return null; + } + + /** + * This will set the numbers that shall represent the alternating horizontal and vertical + * coordinates. + * + * @param points an array with the numbers that shall represent the alternating horizontal and + * vertical coordinates. + */ + public void setVertices(float[] points) + { + COSArray ar = new COSArray(); + ar.setFloatArray(points); + getCOSObject().setItem(COSName.VERTICES, ar); + } + + + /** + * PDF 2.0: This will retrieve the arrays that shall represent the alternating horizontal + * and vertical coordinates for path building. + * + * @return An array of float arrays, each supplying the operands for a path building operator + * (m, l or c). The first array should have 2 elements, the others should have 2 or 6 elements. + */ + public float[][] getPath() + { + COSBase base = getCOSObject().getDictionaryObject(COSName.PATH); + if (base instanceof COSArray) + { + COSArray array = (COSArray) base; + float[][] pathArray = new float[array.size()][]; + for (int i = 0; i < array.size(); ++i) + { + COSBase base2 = array.getObject(i); + if (base2 instanceof COSArray) + { + pathArray[i] = ((COSArray) array.getObject(i)).toFloatArray(); + } + else + { + pathArray[i] = new float[0]; + } + } + return pathArray; + } + return null; + } + + /** + * Set a custom appearance handler for generating the annotations appearance streams. + * + * @param appearanceHandler + */ + public void setCustomAppearanceHandler(PDAppearanceHandler appearanceHandler) + { + customAppearanceHandler = appearanceHandler; + } + + @Override + public void constructAppearances() + { + if (customAppearanceHandler == null) + { + if (SUB_TYPE_POLYGON.equals(getSubtype())) + { + PDPolygonAppearanceHandler appearanceHandler = new PDPolygonAppearanceHandler(this); + appearanceHandler.generateAppearanceStreams(); + } + else if (SUB_TYPE_POLYLINE.equals(getSubtype())) + { + PDPolygonAppearanceHandler appearanceHandler = new PDPolygonAppearanceHandler(this); + appearanceHandler.generateAppearanceStreams(); + } + } + else + { + customAppearanceHandler.generateAppearanceStreams(); + } + } + + } Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolygonAppearanceHandler.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolygonAppearanceHandler.java?rev=1861466&view=auto ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolygonAppearanceHandler.java (added) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolygonAppearanceHandler.java Sun Jun 16 16:02:10 2019 @@ -0,0 +1,236 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.pdfbox.pdmodel.interactive.annotation.handlers; + +import java.io.IOException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSNumber; +import org.apache.pdfbox.io.IOUtils; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.PDAppearanceContentStream; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderEffectDictionary; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; + +/** + * Handler to generate the polygon annotations appearance. + * + */ +public class PDPolygonAppearanceHandler extends PDAbstractAppearanceHandler +{ + private static final Log LOG = LogFactory.getLog(PDPolygonAppearanceHandler.class); + + public PDPolygonAppearanceHandler(PDAnnotation annotation) + { + super(annotation); + } + + @Override + public void generateAppearanceStreams() + { + generateNormalAppearance(); + generateRolloverAppearance(); + generateDownAppearance(); + } + + @Override + public void generateNormalAppearance() + { + PDAnnotationMarkup annotation = (PDAnnotationMarkup) getAnnotation(); + float lineWidth = getLineWidth(); + PDRectangle rect = annotation.getRectangle(); + + // Adjust rectangle even if not empty + // CTAN-example-Annotations.pdf p2 + float minX = Float.MAX_VALUE; + float minY = Float.MAX_VALUE; + float maxX = Float.MIN_VALUE; + float maxY = Float.MIN_VALUE; + + float[][] pathArray = getPathArray(annotation); + if (pathArray == null) + { + return; + } + for (int i = 0; i < pathArray.length; ++i) + { + for (int j = 0; j < pathArray[i].length / 2; ++j) + { + float x = pathArray[i][j * 2]; + float y = pathArray[i][j * 2 + 1]; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + } + + rect.setLowerLeftX(Math.min(minX - lineWidth, rect.getLowerLeftX())); + rect.setLowerLeftY(Math.min(minY - lineWidth, rect.getLowerLeftY())); + rect.setUpperRightX(Math.max(maxX + lineWidth, rect.getUpperRightX())); + rect.setUpperRightY(Math.max(maxY + lineWidth, rect.getUpperRightY())); + annotation.setRectangle(rect); + + PDAppearanceContentStream contentStream = null; + + try + { + contentStream = getNormalAppearanceAsContentStream(); + + boolean hasStroke = contentStream.setStrokingColorOnDemand(getColor()); + + boolean hasBackground = contentStream + .setNonStrokingColorOnDemand(annotation.getInteriorColor()); + + setOpacity(contentStream, annotation.getConstantOpacity()); + + contentStream.setBorderLine(lineWidth, annotation.getBorderStyle(), annotation.getBorder()); + + PDBorderEffectDictionary borderEffect = annotation.getBorderEffect(); + if (borderEffect != null && borderEffect.getStyle().equals(PDBorderEffectDictionary.STYLE_CLOUDY)) + { + CloudyBorder cloudyBorder = new CloudyBorder(contentStream, + borderEffect.getIntensity(), lineWidth, getRectangle()); + cloudyBorder.createCloudyPolygon(pathArray); + annotation.setRectangle(cloudyBorder.getRectangle()); + PDAppearanceStream appearanceStream = annotation.getNormalAppearanceStream(); + appearanceStream.setBBox(cloudyBorder.getBBox()); + appearanceStream.setMatrix(cloudyBorder.getMatrix()); + } + else + { + // Acrobat applies a padding to each side of the bbox so the line is + // completely within the bbox. + + for (int i = 0; i < pathArray.length; i++) + { + float[] pointsArray = pathArray[i]; + // first array shall be of size 2 and specify the moveto operator + if (i == 0 && pointsArray.length == 2) + { + contentStream.moveTo(pointsArray[0], pointsArray[1]); + } + else + { + // entries of length 2 shall be treated as lineto operator + if (pointsArray.length == 2) + { + contentStream.lineTo(pointsArray[0], pointsArray[1]); + } + else if (pointsArray.length == 6) + { + contentStream.curveTo(pointsArray[0], pointsArray[1], + pointsArray[2], pointsArray[3], + pointsArray[4], pointsArray[5]); + } + } + } + contentStream.closePath(); + } + contentStream.drawShape(lineWidth, hasStroke, hasBackground); + } + catch (IOException e) + { + LOG.error(e); + } + finally + { + IOUtils.closeQuietly(contentStream); + } + } + + private float[][] getPathArray(PDAnnotationMarkup annotation) + { + // PDF 2.0: Path takes priority over Vertices + float[][] pathArray = annotation.getPath(); + if (pathArray == null) + { + // convert PDF 1.* array to PDF 2.0 array + float[] verticesArray = annotation.getVertices(); + if (verticesArray == null) + { + return null; + } + int points = verticesArray.length / 2; + pathArray = new float[points][2]; + for (int i = 0; i < points; ++i) + { + pathArray[i][0] = verticesArray[i * 2]; + pathArray[i][1] = verticesArray[i * 2 + 1]; + } + } + return pathArray; + } + + @Override + public void generateRolloverAppearance() + { + // No rollover appearance generated for a polygon annotation + } + + @Override + public void generateDownAppearance() + { + // No down appearance generated for a polygon annotation + } + + /** + * Get the line with of the border. + * + * Get the width of the line used to draw a border around the annotation. + * This may either be specified by the annotation dictionaries Border + * setting or by the W entry in the BS border style dictionary. If both are + * missing the default width is 1. + * + * @return the line width + */ + // TODO: according to the PDF spec the use of the BS entry is annotation + // specific + // so we will leave that to be implemented by individual handlers. + // If at the end all annotations support the BS entry this can be handled + // here and removed from the individual handlers. + float getLineWidth() + { + PDAnnotationMarkup annotation = (PDAnnotationMarkup) getAnnotation(); + + PDBorderStyleDictionary bs = annotation.getBorderStyle(); + + if (bs != null) + { + return bs.getWidth(); + } + + COSArray borderCharacteristics = annotation.getBorder(); + if (borderCharacteristics.size() >= 3) + { + COSBase base = borderCharacteristics.getObject(2); + if (base instanceof COSNumber) + { + return ((COSNumber) base).floatValue(); + } + } + + return 1; + } +} Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java?rev=1861466&view=auto ============================================================================== --- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java (added) +++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDPolylineAppearanceHandler.java Sun Jun 16 16:02:10 2019 @@ -0,0 +1,263 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.pdfbox.pdmodel.interactive.annotation.handlers; + +import java.io.IOException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.pdfbox.cos.COSArray; +import org.apache.pdfbox.cos.COSBase; +import org.apache.pdfbox.cos.COSNumber; +import org.apache.pdfbox.io.IOUtils; +import org.apache.pdfbox.pdmodel.common.PDRectangle; +import org.apache.pdfbox.pdmodel.graphics.color.PDColor; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; +import org.apache.pdfbox.pdmodel.PDAppearanceContentStream; +import static org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLine.LE_NONE; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup; +import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary; +import org.apache.pdfbox.util.Matrix; + +/** + * Handler to generate the polyline annotations appearance. + * + */ +public class PDPolylineAppearanceHandler extends PDAbstractAppearanceHandler +{ + private static final Log LOG = LogFactory.getLog(PDPolylineAppearanceHandler.class); + + public PDPolylineAppearanceHandler(PDAnnotation annotation) + { + super(annotation); + } + + @Override + public void generateAppearanceStreams() + { + generateNormalAppearance(); + generateRolloverAppearance(); + generateDownAppearance(); + } + + @Override + public void generateNormalAppearance() + { + PDAnnotationMarkup annotation = (PDAnnotationMarkup) getAnnotation(); + PDRectangle rect = annotation.getRectangle(); + float[] pathsArray = annotation.getVertices(); + if (pathsArray == null || pathsArray.length < 4) + { + return; + } + AnnotationBorder ab = AnnotationBorder.getAnnotationBorder(annotation, annotation.getBorderStyle()); + PDColor color = annotation.getColor(); + if (color == null || color.getComponents().length == 0 || Float.compare(ab.width, 0) == 0) + { + return; + } + + // Adjust rectangle even if not empty + // CTAN-example-Annotations.pdf and pdf_commenting_new.pdf p11 + //TODO in a class structure this should be overridable + float minX = Float.MAX_VALUE; + float minY = Float.MAX_VALUE; + float maxX = Float.MIN_VALUE; + float maxY = Float.MIN_VALUE; + for (int i = 0; i < pathsArray.length / 2; ++i) + { + float x = pathsArray[i * 2]; + float y = pathsArray[i * 2 + 1]; + minX = Math.min(minX, x); + minY = Math.min(minY, y); + maxX = Math.max(maxX, x); + maxY = Math.max(maxY, y); + } + // 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); + + PDAppearanceContentStream cs = null; + + try + { + cs = getNormalAppearanceAsContentStream(); + + boolean hasBackground = cs.setNonStrokingColorOnDemand(annotation.getInteriorColor()); + setOpacity(cs, annotation.getConstantOpacity()); + boolean hasStroke = cs.setStrokingColorOnDemand(color); + + if (ab.dashArray != null) + { + cs.setLineDashPattern(ab.dashArray, 0); + } + cs.setLineWidth(ab.width); + + for (int i = 0; i < pathsArray.length / 2; ++i) + { + float x = pathsArray[i * 2]; + float y = pathsArray[i * 2 + 1]; + if (i == 0) + { + 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]; + float len = (float) (Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2))); + if (Float.compare(len, 0) != 0) + { + x += (x1 - x) / len * ab.width; + y += (y1 - y) / len * ab.width; + } + } + cs.moveTo(x, y); + } + else + { + 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]; + float len = (float) (Math.sqrt(Math.pow(x0 - x, 2) + Math.pow(y0 - y, 2))); + if (Float.compare(len, 0) != 0) + { + x -= (x - x0) / len * ab.width; + y -= (y - y0) / len * ab.width; + } + } + cs.lineTo(x, y); + } + } + 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 + + // paint the styles here and 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(); + if (ANGLED_STYLES.contains(annotation.getStartPointEndingStyle())) + { + double angle = Math.atan2(y2 - y1, x2 - x1); + cs.transform(Matrix.getRotateInstance(angle, x1, y1)); + } + else + { + cs.transform(Matrix.getTranslateInstance(x1, y1)); + } + drawStyle(annotation.getStartPointEndingStyle(), cs, 0, 0, ab.width, hasStroke, hasBackground, false); + 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 + if (ANGLED_STYLES.contains(annotation.getEndPointEndingStyle())) + { + double angle = Math.atan2(y2 - y1, x2 - x1); + cs.transform(Matrix.getRotateInstance(angle, x2, y2)); + } + else + { + cs.transform(Matrix.getTranslateInstance(x2, y2)); + } + drawStyle(annotation.getEndPointEndingStyle(), cs, 0, 0, ab.width, hasStroke, hasBackground, true); + } + } + catch (IOException ex) + { + LOG.error(ex); + } + finally + { + IOUtils.closeQuietly(cs); + } + } + + @Override + public void generateRolloverAppearance() + { + // No rollover appearance generated for a polyline annotation + } + + @Override + public void generateDownAppearance() + { + // No down appearance generated for a polyline annotation + } + + //TODO DRY, this code is from polygonAppearanceHandler so it's double + + /** + * Get the line with of the border. + * + * Get the width of the line used to draw a border around the annotation. + * This may either be specified by the annotation dictionaries Border + * setting or by the W entry in the BS border style dictionary. If both are + * missing the default width is 1. + * + * @return the line width + */ + // TODO: according to the PDF spec the use of the BS entry is annotation + // specific + // so we will leave that to be implemented by individual handlers. + // If at the end all annotations support the BS entry this can be handled + // here and removed from the individual handlers. + float getLineWidth() + { + PDAnnotationMarkup annotation = (PDAnnotationMarkup) getAnnotation(); + + PDBorderStyleDictionary bs = annotation.getBorderStyle(); + + if (bs != null) + { + return bs.getWidth(); + } + + COSArray borderCharacteristics = annotation.getBorder(); + if (borderCharacteristics.size() >= 3) + { + COSBase base = borderCharacteristics.getObject(2); + if (base instanceof COSNumber) + { + return ((COSNumber) base).floatValue(); + } + } + + return 1; + } +}