You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pdfbox.apache.org by ms...@apache.org on 2019/06/16 16:36:19 UTC
svn commit: r1861469 - in
/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation:
PDAnnotationMarkup.java handlers/PDFreeTextAppearanceHandler.java
handlers/PDInkAppearanceHandler.java
Author: msahyoun
Date: Sun Jun 16 16:36:19 2019
New Revision: 1861469
URL: http://svn.apache.org/viewvc?rev=1861469&view=rev
Log:
PDFBOX-4574: add support for Ink appearance handler
Added:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDFreeTextAppearanceHandler.java
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDInkAppearanceHandler.java
Modified:
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/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=1861469&r1=1861468&r2=1861469&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:36:19 2019
@@ -31,6 +31,7 @@ import org.apache.pdfbox.pdmodel.graphic
import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDAppearanceHandler;
import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDCaretAppearanceHandler;
import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDFreeTextAppearanceHandler;
+import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDInkAppearanceHandler;
import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDPolygonAppearanceHandler;
import org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDPolylineAppearanceHandler;
@@ -462,6 +463,60 @@ public class PDAnnotationMarkup extends
}
/**
+ * Sets the paths that make this annotation.
+ *
+ * @param inkList An array of arrays, each representing a stroked path. Each array shall be a
+ * series of alternating horizontal and vertical coordinates. If the parameter is null the entry
+ * will be removed.
+ */
+ public void setInkList(float[][] inkList)
+ {
+ if (inkList == null)
+ {
+ getCOSObject().removeItem(COSName.INKLIST);
+ return;
+ }
+ COSArray array = new COSArray();
+ for (float[] path : inkList)
+ {
+ COSArray innerArray = new COSArray();
+ innerArray.setFloatArray(path);
+ array.add(innerArray);
+ }
+ getCOSObject().setItem(COSName.INKLIST, array);
+ }
+
+ /**
+ * Get one or more disjoint paths that make this annotation.
+ *
+ * @return An array of arrays, each representing a stroked path. Each array shall be a series of
+ * alternating horizontal and vertical coordinates.
+ */
+ public float[][] getInkList()
+ {
+ COSBase base = getCOSObject().getDictionaryObject(COSName.INKLIST);
+ if (base instanceof COSArray)
+ {
+ COSArray array = (COSArray) base;
+ float[][] inkList = new float[array.size()][];
+ for (int i = 0; i < array.size(); ++i)
+ {
+ COSBase base2 = array.getObject(i);
+ if (base2 instanceof COSArray)
+ {
+ inkList[i] = ((COSArray) array.getObject(i)).toFloatArray();
+ }
+ else
+ {
+ inkList[i] = new float[0];
+ }
+ }
+ return inkList;
+ }
+ return new float[0][0];
+ }
+
+ /**
* Get the default appearance.
*
* @return a string describing the default appearance.
@@ -809,6 +864,10 @@ public class PDAnnotationMarkup extends
{
appearanceHandler = new PDFreeTextAppearanceHandler(this);
}
+ else if (SUB_TYPE_INK.equals(getSubtype()))
+ {
+ appearanceHandler = new PDInkAppearanceHandler(this);
+ }
else if (SUB_TYPE_POLYGON.equals(getSubtype()))
{
appearanceHandler = new PDPolygonAppearanceHandler(this);
Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDFreeTextAppearanceHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDFreeTextAppearanceHandler.java?rev=1861469&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDFreeTextAppearanceHandler.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDFreeTextAppearanceHandler.java Sun Jun 16 16:36:19 2019
@@ -0,0 +1,447 @@
+/*
+ * Copyright 2018 The Apache Software Foundation.
+ *
+ * Licensed 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.fontbox.util.Charsets;
+import org.apache.pdfbox.contentstream.operator.Operator;
+import org.apache.pdfbox.cos.COSArray;
+import org.apache.pdfbox.cos.COSBase;
+import org.apache.pdfbox.cos.COSName;
+import org.apache.pdfbox.cos.COSNumber;
+import org.apache.pdfbox.cos.COSObject;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdfparser.PDFStreamParser;
+import org.apache.pdfbox.pdmodel.PDAppearanceContentStream;
+import org.apache.pdfbox.pdmodel.common.PDRectangle;
+import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDType1Font;
+import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceCMYK;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceGray;
+import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
+import static org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationLine.LE_NONE;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderEffectDictionary;
+import static org.apache.pdfbox.pdmodel.interactive.annotation.handlers.PDAbstractAppearanceHandler.SHORT_STYLES;
+import org.apache.pdfbox.pdmodel.interactive.annotation.layout.AppearanceStyle;
+import org.apache.pdfbox.pdmodel.interactive.annotation.layout.PlainText;
+import org.apache.pdfbox.pdmodel.interactive.annotation.layout.PlainTextFormatter;
+import org.apache.pdfbox.util.Matrix;
+
+public class PDFreeTextAppearanceHandler extends PDAbstractAppearanceHandler
+{
+ private static final Log LOG = LogFactory.getLog(PDFreeTextAppearanceHandler.class);
+
+ public PDFreeTextAppearanceHandler(PDAnnotation annotation)
+ {
+ super(annotation);
+ }
+
+ @Override
+ public void generateAppearanceStreams()
+ {
+ generateNormalAppearance();
+ generateRolloverAppearance();
+ generateDownAppearance();
+ }
+
+ @Override
+ public void generateNormalAppearance()
+ {
+ PDAnnotationMarkup annotation = (PDAnnotationMarkup) getAnnotation();
+ float[] pathsArray = new float[0];
+ if (PDAnnotationMarkup.IT_FREE_TEXT_CALLOUT.equals(annotation.getIntent()))
+ {
+ pathsArray = annotation.getCallout();
+ if (pathsArray == null || pathsArray.length != 4 && pathsArray.length != 6)
+ {
+ pathsArray = new float[0];
+ }
+ }
+ AnnotationBorder ab = AnnotationBorder.getAnnotationBorder(annotation, annotation.getBorderStyle());
+
+ PDAppearanceContentStream cs = null;
+
+ try
+ {
+ cs = getNormalAppearanceAsContentStream(true);
+
+ // The fill color is the /C entry, there is no /IC entry defined
+ boolean hasBackground = cs.setNonStrokingColorOnDemand(annotation.getColor());
+ setOpacity(cs, annotation.getConstantOpacity());
+
+ // Adobe uses the last non stroking color from /DA as stroking color!
+ PDColor strokingColor = extractNonStrokingColor(annotation);
+ boolean hasStroke = cs.setStrokingColorOnDemand(strokingColor);
+
+ if (ab.dashArray != null)
+ {
+ cs.setLineDashPattern(ab.dashArray, 0);
+ }
+ cs.setLineWidth(ab.width);
+
+ // draw callout line(s)
+ // must be done before retangle paint to avoid a line cutting through cloud
+ // see CTAN-example-Annotations.pdf
+ 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.getLineEndingStyle()))
+ {
+ // 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
+ {
+ cs.lineTo(x, y);
+ }
+ }
+ if (pathsArray.length > 0)
+ {
+ cs.stroke();
+ }
+
+ // paint the styles here and after line(s) draw, to avoid line crossing a filled shape
+ if (PDAnnotationMarkup.IT_FREE_TEXT_CALLOUT.equals(annotation.getIntent())
+ // check only needed to avoid q cm Q if LE_NONE
+ && !LE_NONE.equals(annotation.getLineEndingStyle())
+ && pathsArray.length >= 4)
+ {
+ float x2 = pathsArray[2];
+ float y2 = pathsArray[3];
+ float x1 = pathsArray[0];
+ float y1 = pathsArray[1];
+ cs.saveGraphicsState();
+ if (ANGLED_STYLES.contains(annotation.getLineEndingStyle()))
+ {
+ // do a transform so that first "arm" is imagined flat,
+ // like in line handler.
+ // The alternative would be to apply the transform to the
+ // LE shape coordinates directly, which would be more work
+ // and produce code difficult to understand
+ double angle = Math.atan2(y2 - y1, x2 - x1);
+ cs.transform(Matrix.getRotateInstance(angle, x1, y1));
+ }
+ else
+ {
+ cs.transform(Matrix.getTranslateInstance(x1, y1));
+ }
+ drawStyle(annotation.getLineEndingStyle(), cs, 0, 0, ab.width, hasStroke, hasBackground, false);
+ cs.restoreGraphicsState();
+ }
+
+ PDRectangle borderBox;
+ PDBorderEffectDictionary borderEffect = annotation.getBorderEffect();
+ if (borderEffect != null && borderEffect.getStyle().equals(PDBorderEffectDictionary.STYLE_CLOUDY))
+ {
+ // Adobe draws the text with the original rectangle in mind.
+ // but if there is an /RD, then writing area get smaller.
+ // do this here because /RD is overwritten in a few lines
+ borderBox = applyRectDifferences(getRectangle(), annotation.getRectDifferences());
+
+ //TODO this segment was copied from square handler. Refactor?
+ CloudyBorder cloudyBorder = new CloudyBorder(cs,
+ borderEffect.getIntensity(), ab.width, getRectangle());
+ cloudyBorder.createCloudyRectangle(annotation.getRectDifference());
+ annotation.setRectangle(cloudyBorder.getRectangle());
+ annotation.setRectDifference(cloudyBorder.getRectDifference());
+ PDAppearanceStream appearanceStream = annotation.getNormalAppearanceStream();
+ appearanceStream.setBBox(cloudyBorder.getBBox());
+ appearanceStream.setMatrix(cloudyBorder.getMatrix());
+ }
+ else
+ {
+ // handle the border box
+ //
+ // There are two options. The handling is not part of the PDF specification but
+ // implementation specific to Adobe Reader
+ // - if /RD is set the border box is the /Rect entry inset by the respective
+ // border difference.
+ // - if /RD is not set then we don't touch /RD etc because Adobe doesn't either.
+ borderBox = applyRectDifferences(getRectangle(), annotation.getRectDifferences());
+ annotation.getNormalAppearanceStream().setBBox(borderBox);
+
+ // note that borderBox is not modified
+ PDRectangle paddedRectangle = getPaddedRectangle(borderBox, ab.width / 2);
+ cs.addRect(paddedRectangle.getLowerLeftX(), paddedRectangle.getLowerLeftY(),
+ paddedRectangle.getWidth(), paddedRectangle.getHeight());
+ }
+ cs.drawShape(ab.width, hasStroke, hasBackground);
+
+ // rotation is an undocumented feature, but Adobe uses it. Examples can be found
+ // in pdf_commenting_new.pdf file, page 3.
+ int rotation = annotation.getCOSObject().getInt(COSName.ROTATE, 0);
+ cs.transform(Matrix.getRotateInstance(Math.toRadians(rotation), 0, 0));
+ float xOffset;
+ float yOffset;
+ float width = rotation == 90 || rotation == 270 ? borderBox.getHeight() : borderBox.getWidth();
+ // strategy to write formatted text is somewhat inspired by
+ // AppearanceGeneratorHelper.insertGeneratedAppearance()
+ PDFont font = PDType1Font.HELVETICA;
+ float clipY;
+ float clipWidth = width - ab.width * 4;
+ float clipHeight = rotation == 90 || rotation == 270 ?
+ borderBox.getWidth() - ab.width * 4 : borderBox.getHeight() - ab.width * 4;
+ float fontSize = extractFontSize(annotation);
+
+ // value used by Adobe, no idea where it comes from, actual font bbox max y is 0.931
+ // gathered by creating an annotation with width 0.
+ float yDelta = 0.7896f;
+ switch (rotation)
+ {
+ case 180:
+ xOffset = - borderBox.getUpperRightX() + ab.width * 2;
+ yOffset = - borderBox.getLowerLeftY() - ab.width * 2 - yDelta * fontSize;
+ clipY = - borderBox.getUpperRightY() + ab.width * 2;
+ break;
+ case 90:
+ xOffset = borderBox.getLowerLeftY() + ab.width * 2;
+ yOffset = - borderBox.getLowerLeftX() - ab.width * 2 - yDelta * fontSize;
+ clipY = - borderBox.getUpperRightX() + ab.width * 2;
+ break;
+ case 270:
+ xOffset = - borderBox.getUpperRightY() + ab.width * 2;
+ yOffset = borderBox.getUpperRightX() - ab.width * 2 - yDelta * fontSize;
+ clipY = borderBox.getLowerLeftX() + ab.width * 2;
+ break;
+ case 0:
+ default:
+ xOffset = borderBox.getLowerLeftX() + ab.width * 2;
+ yOffset = borderBox.getUpperRightY() - ab.width * 2 - yDelta * fontSize;
+ clipY = borderBox.getLowerLeftY() + ab.width * 2;
+ break;
+ }
+
+ // clip writing area
+ cs.addRect(xOffset, clipY, clipWidth, clipHeight);
+ cs.clip();
+
+ cs.beginText();
+ cs.setFont(font, fontSize);
+ cs.setNonStrokingColor(strokingColor.getComponents());
+ AppearanceStyle appearanceStyle = new AppearanceStyle();
+ appearanceStyle.setFont(font);
+ appearanceStyle.setFontSize(fontSize);
+ PlainTextFormatter formatter = new PlainTextFormatter.Builder(cs)
+ .style(appearanceStyle)
+ .text(new PlainText(annotation.getContents()))
+ .width(width - ab.width * 4)
+ .wrapLines(true)
+ .initialOffset(xOffset, yOffset)
+ // Adobe ignores the /Q
+ //.textAlign(annotation.getQ())
+ .build();
+ formatter.format();
+ cs.endText();
+
+
+ if (pathsArray.length > 0)
+ {
+ PDRectangle rect = getRectangle();
+
+ // Adjust rectangle
+ // important to do this after the rectangle has been painted, because the
+ // final rectangle will be bigger due to callout
+ // CTAN-example-Annotations.pdf p1
+ //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);
+
+ // need to set the BBox too, because rectangle modification came later
+ annotation.getNormalAppearanceStream().setBBox(getRectangle());
+
+ //TODO when callout is used, /RD should be so that the result is the writable part
+ }
+ }
+ catch (IOException ex)
+ {
+ LOG.error(ex);
+ }
+ finally
+ {
+ IOUtils.closeQuietly(cs);
+ }
+ }
+
+ // get the last non stroking color from the /DA entry
+ private PDColor extractNonStrokingColor(PDAnnotationMarkup annotation)
+ {
+ // It could also work with a regular expression, but that should be written so that
+ // "/LucidaConsole 13.94766 Tf .392 .585 .93 rg" does not produce "2 .585 .93 rg" as result
+ // Another alternative might be to create a PDDocument and a PDPage with /DA content as /Content,
+ // process the whole thing and then get the non stroking color.
+
+ PDColor strokingColor = new PDColor(new float[]{0}, PDDeviceGray.INSTANCE);
+ String defaultAppearance = annotation.getDefaultAppearance();
+ if (defaultAppearance == null)
+ {
+ return strokingColor;
+ }
+
+ try
+ {
+ // not sure if charset is correct, but we only need numbers and simple characters
+ PDFStreamParser parser = new PDFStreamParser(defaultAppearance.getBytes(Charsets.US_ASCII));
+ COSArray arguments = new COSArray();
+ COSArray colors = null;
+ Operator graphicOp = null;
+ for (Object token = parser.parseNextToken(); token != null; token = parser.parseNextToken())
+ {
+ if (token instanceof COSObject)
+ {
+ arguments.add(((COSObject) token).getObject());
+ }
+ else if (token instanceof Operator)
+ {
+ Operator op = (Operator) token;
+ String name = op.getName();
+ if ("g".equals(name) || "rg".equals(name) || "k".equals(name))
+ {
+ graphicOp = op;
+ colors = arguments;
+ }
+ arguments = new COSArray();
+ }
+ else
+ {
+ arguments.add((COSBase) token);
+ }
+ }
+ if (graphicOp != null)
+ {
+ String graphicOpName = graphicOp.getName();
+ if ("g".equals(graphicOpName))
+ {
+ strokingColor = new PDColor(colors, PDDeviceGray.INSTANCE);
+ }
+ else if ("rg".equals(graphicOpName))
+ {
+ strokingColor = new PDColor(colors, PDDeviceRGB.INSTANCE);
+ }
+ else if ("k".equals(graphicOpName))
+ {
+ strokingColor = new PDColor(colors, PDDeviceCMYK.INSTANCE);
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ LOG.warn("Problem parsing /DA, will use default black", ex);
+ }
+ return strokingColor;
+ }
+
+ //TODO extractNonStrokingColor and extractFontSize
+ // might somehow be replaced with PDDefaultAppearanceString,
+ // which is quite similar.
+ private float extractFontSize(PDAnnotationMarkup annotation)
+ {
+ String defaultAppearance = annotation.getDefaultAppearance();
+ if (defaultAppearance == null)
+ {
+ return 10;
+ }
+
+ try
+ {
+ // not sure if charset is correct, but we only need numbers and simple characters
+ PDFStreamParser parser = new PDFStreamParser(defaultAppearance.getBytes(Charsets.US_ASCII));
+ COSArray arguments = new COSArray();
+ COSArray fontArguments = new COSArray();
+ for (Object token = parser.parseNextToken(); token != null; token = parser.parseNextToken())
+ {
+ if (token instanceof COSObject)
+ {
+ arguments.add(((COSObject) token).getObject());
+ }
+ else if (token instanceof Operator)
+ {
+ Operator op = (Operator) token;
+ String name = op.getName();
+ if ("Tf".equals(name))
+ {
+ fontArguments = arguments;
+ }
+ arguments = new COSArray();
+ }
+ else
+ {
+ arguments.add((COSBase) token);
+ }
+ }
+ if (fontArguments.size() >= 2)
+ {
+ COSBase base = fontArguments.get(1);
+ if (base instanceof COSNumber)
+ {
+ return ((COSNumber) base).floatValue();
+ }
+ }
+ }
+ catch (IOException ex)
+ {
+ LOG.warn("Problem parsing /DA, will use default 10", ex);
+ }
+ return 10;
+ }
+
+ @Override
+ public void generateRolloverAppearance()
+ {
+ // TODO to be implemented
+ }
+
+ @Override
+ public void generateDownAppearance()
+ {
+ // TODO to be implemented
+ }
+}
Added: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDInkAppearanceHandler.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDInkAppearanceHandler.java?rev=1861469&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDInkAppearanceHandler.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/annotation/handlers/PDInkAppearanceHandler.java Sun Jun 16 16:36:19 2019
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2018 The Apache Software Foundation.
+ *
+ * Licensed 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.pdmodel.graphics.color.PDColor;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation;
+import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationMarkup;
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDAppearanceContentStream;
+
+/**
+ * Handler to generate the ink annotations appearance.
+ *
+ */
+public class PDInkAppearanceHandler extends PDAbstractAppearanceHandler
+{
+ private static final Log LOG = LogFactory.getLog(PDInkAppearanceHandler.class);
+
+ public PDInkAppearanceHandler(PDAnnotation annotation)
+ {
+ super(annotation);
+ }
+
+ @Override
+ public void generateAppearanceStreams()
+ {
+ generateNormalAppearance();
+ generateRolloverAppearance();
+ generateDownAppearance();
+ }
+
+ @Override
+ public void generateNormalAppearance()
+ {
+ PDAnnotationMarkup ink = (PDAnnotationMarkup) getAnnotation();
+ // PDF spec does not mention /Border for ink annotations, but it is used if /BS is not available
+ AnnotationBorder ab = AnnotationBorder.getAnnotationBorder(ink, ink.getBorderStyle());
+ PDColor color = ink.getColor();
+ if (color == null || color.getComponents().length == 0 || Float.compare(ab.width, 0) == 0)
+ {
+ return;
+ }
+
+ PDAppearanceContentStream cs = null;
+
+ try
+ {
+ cs = getNormalAppearanceAsContentStream();
+
+ setOpacity(cs, ink.getConstantOpacity());
+
+ cs.setStrokingColor(color);
+ if (ab.dashArray != null)
+ {
+ cs.setLineDashPattern(ab.dashArray, 0);
+ }
+ cs.setLineWidth(ab.width);
+
+ for (float[] pathArray : ink.getInkList())
+ {
+ int nPoints = pathArray.length / 2;
+
+ // "When drawn, the points shall be connected by straight lines or curves
+ // in an implementation-dependent way" - we do lines.
+ for (int i = 0; i < nPoints; ++i)
+ {
+ float x = pathArray[i * 2];
+ float y = pathArray[i * 2 + 1];
+
+ if (i == 0)
+ {
+ cs.moveTo(x, y);
+ }
+ else
+ {
+ cs.lineTo(x, y);
+ }
+ }
+ cs.stroke();
+ }
+ }
+ catch (IOException ex)
+ {
+ LOG.error(ex);
+ }
+ finally
+ {
+ IOUtils.closeQuietly(cs);
+ }
+ }
+
+ @Override
+ public void generateRolloverAppearance()
+ {
+ // No rollover appearance generated
+ }
+
+ @Override
+ public void generateDownAppearance()
+ {
+ // No down appearance generated
+ }
+}
\ No newline at end of file