org.sejda.sambox.pdmodel.interactive.annotation.PDAnnotationMarkup Maven / Gradle / Ivy
Show all versions of sambox Show documentation
/*
* 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.sejda.sambox.pdmodel.interactive.annotation;
import static java.util.Optional.ofNullable;
import java.io.IOException;
import java.util.Calendar;
import org.sejda.sambox.cos.COSArray;
import org.sejda.sambox.cos.COSBase;
import org.sejda.sambox.cos.COSDictionary;
import org.sejda.sambox.cos.COSFloat;
import org.sejda.sambox.cos.COSName;
import org.sejda.sambox.cos.COSStream;
import org.sejda.sambox.cos.COSString;
import org.sejda.sambox.pdmodel.common.PDRectangle;
import org.sejda.sambox.pdmodel.graphics.color.PDColor;
import org.sejda.sambox.pdmodel.interactive.annotation.handlers.PDAppearanceHandler;
import org.sejda.sambox.pdmodel.interactive.annotation.handlers.PDCaretAppearanceHandler;
import org.sejda.sambox.pdmodel.interactive.annotation.handlers.PDFileAttachmentAppearanceHandler;
import org.sejda.sambox.pdmodel.interactive.annotation.handlers.PDFreeTextAppearanceHandler;
import org.sejda.sambox.pdmodel.interactive.annotation.handlers.PDInkAppearanceHandler;
import org.sejda.sambox.pdmodel.interactive.annotation.handlers.PDPolygonAppearanceHandler;
import org.sejda.sambox.pdmodel.interactive.annotation.handlers.PDPolylineAppearanceHandler;
import org.sejda.sambox.pdmodel.interactive.annotation.handlers.PDSoundAppearanceHandler;
import org.sejda.sambox.pdmodel.interactive.form.PDVariableText;
/**
* This class represents the additonal fields of a Markup type Annotation. See section 12.5.6 of
* ISO32000-1:2008 (starting with page 390) for details on annotation types.
*
* @author Paul King
*/
public class PDAnnotationMarkup extends PDAnnotation
{
private PDAppearanceHandler customAppearanceHandler;
/**
* Constant for a FreeText type of annotation.
*/
public static final String SUB_TYPE_FREETEXT = "FreeText";
/**
* Constant for an Polygon type of annotation.
*/
public static final String SUB_TYPE_POLYGON = "Polygon";
/**
* Constant for an PolyLine type of annotation.
*/
public static final String SUB_TYPE_POLYLINE = "PolyLine";
/**
* Constant for an Caret type of annotation.
*/
public static final String SUB_TYPE_CARET = "Caret";
/**
* Constant for an Ink type of annotation.
*/
public static final String SUB_TYPE_INK = "Ink";
/**
* Constant for an Sound type of annotation.
*/
public static final String SUB_TYPE_SOUND = "Sound";
/*
* The various values of the free text annotation as defined in the PDF 1.7 reference Table 170
*/
/**
* A plain free-text annotation, also known as a text box comment.
*/
public static final String IT_FREE_TEXT = "FreeText";
/**
* A callout, associated with an area on the page through the callout line specified.
*/
public static final String IT_FREE_TEXT_CALLOUT = "FreeTextCallout";
/**
* The annotation is intended to function as a click-to-type or typewriter object.
*/
public static final String IT_FREE_TEXT_TYPE_WRITER = "FreeTextTypeWriter";
/*
* The various values of the reply type as defined in the PDF 1.7 reference Table 170
*/
/**
* Constant for an annotation reply type.
*/
public static final String RT_REPLY = "R";
/**
* Constant for an annotation reply type.
*/
public static final String RT_GROUP = "Group";
public PDAnnotationMarkup()
{
}
/**
* Constructor.
*
* @param dict The annotations dictionary.
*/
public PDAnnotationMarkup(COSDictionary dict)
{
super(dict);
}
/**
* Retrieve the string used as the title of the popup window shown when open and active (by
* convention this identifies who added the annotation).
*
* @return The title of the popup.
*/
public String getTitlePopup()
{
return getCOSObject().getString(COSName.T);
}
/**
* Set the string used as the title of the popup window shown when open and active (by
* convention this identifies who added the annotation).
*
* @param t The title of the popup.
*/
public void setTitlePopup(String t)
{
getCOSObject().setString(COSName.T, t);
}
/**
* This will retrieve the popup annotation used for entering/editing the text for this
* annotation.
*
* @return the popup annotation.
*/
public PDAnnotationPopup getPopup()
{
return ofNullable(
getCOSObject().getDictionaryObject(COSName.POPUP, COSDictionary.class)).map(
PDAnnotationPopup::new).orElse(null);
}
/**
* This will set the popup annotation used for entering/editing the text for this annotation.
*
* @param popup the popup annotation.
*/
public void setPopup(PDAnnotationPopup popup)
{
getCOSObject().setItem(COSName.POPUP, popup);
}
/**
* This will retrieve the constant opacity value used when rendering the annotation (excluing
* any popup).
*
* @return the constant opacity value.
*/
public float getConstantOpacity()
{
return getCOSObject().getFloat(COSName.CA, 1);
}
/**
* This will set the constant opacity value used when rendering the annotation (excluing any
* popup).
*
* @param ca the constant opacity value.
*/
public void setConstantOpacity(float ca)
{
getCOSObject().setFloat(COSName.CA, ca);
}
/**
* This will retrieve the rich text stream which is displayed in the popup window.
*
* @return the rich text stream.
*/
public String getRichContents()
{
COSBase base = getCOSObject().getDictionaryObject(COSName.RC);
if (base instanceof COSString)
{
return ((COSString) base).getString();
}
if (base instanceof COSStream)
{
return ((COSStream) base).asTextString();
}
return null;
}
/**
* This will set the rich text stream which is displayed in the popup window.
*
* @param rc the rich text stream.
*/
public void setRichContents(String rc)
{
getCOSObject().setItem(COSName.RC, COSString.parseLiteral(rc));
}
/**
* This will retrieve the date and time the annotation was created.
*
* @return the creation date/time.
* @throws IOException if there is a format problem when converting the date.
*/
public Calendar getCreationDate()
{
return getCOSObject().getDate(COSName.CREATION_DATE);
}
/**
* This will set the date and time the annotation was created.
*
* @param creationDate the date and time the annotation was created.
*/
public void setCreationDate(Calendar creationDate)
{
getCOSObject().setDate(COSName.CREATION_DATE, creationDate);
}
/**
* This will retrieve the annotation to which this one is "In Reply To" the actual relationship
* is specified by the RT entry.
*
* @return the other annotation or null if there is none.
* @throws IOException if there is an error creating the other annotation.
*/
public PDAnnotation getInReplyTo()
{
return ofNullable(getCOSObject().getDictionaryObject("IRT", COSDictionary.class)).map(
PDAnnotation::createAnnotation).orElse(null);
}
/**
* This will set the annotation to which this one is "In Reply To" the actual relationship is
* specified by the RT entry.
*
* @param irt the annotation this one is "In Reply To".
*/
public void setInReplyTo(PDAnnotation irt)
{
getCOSObject().setItem("IRT", irt);
}
/**
* This will retrieve the short description of the subject of the annotation.
*
* @return the subject.
*/
public String getSubject()
{
return getCOSObject().getString(COSName.SUBJ);
}
/**
* This will set the short description of the subject of the annotation.
*
* @param subj short description of the subject.
*/
public void setSubject(String subj)
{
getCOSObject().setString(COSName.SUBJ, subj);
}
/**
* This will retrieve the Reply Type (relationship) with the annotation in the IRT entry See the
* RT_* constants for the available values.
*
* @return the relationship.
*/
public String getReplyType()
{
return getCOSObject().getNameAsString("RT", RT_REPLY);
}
/**
* This will set the Reply Type (relationship) with the annotation in the IRT entry See the RT_*
* constants for the available values.
*
* @param rt the reply type.
*/
public void setReplyType(String rt)
{
getCOSObject().setName("RT", rt);
}
/**
* This will retrieve the intent of the annotation The values and meanings are specific to the
* actual annotation See the IT_* constants for the annotation classes.
*
* @return the intent
*/
public String getIntent()
{
return getCOSObject().getNameAsString(COSName.IT);
}
/**
* This will set the intent of the annotation The values and meanings are specific to the actual
* annotation See the IT_* constants for the annotation classes.
*
* @param it the intent
*/
public void setIntent(String it)
{
getCOSObject().setName(COSName.IT, it);
}
/**
* This will return the external data dictionary.
*
* @return the external data dictionary
*/
public PDExternalDataDictionary getExternalData()
{
COSBase exData = this.getCOSObject().getDictionaryObject("ExData");
if (exData instanceof COSDictionary)
{
return new PDExternalDataDictionary((COSDictionary) exData);
}
return null;
}
/**
* This will set the external data dictionary.
*
* @param externalData the external data dictionary
*/
public void setExternalData(PDExternalDataDictionary externalData)
{
this.getCOSObject().setItem("ExData", externalData);
}
/**
* This will set the border style dictionary, specifying the width and dash pattern used in
* drawing the line.
*
* @param bs the border style dictionary to set.
*/
public void setBorderStyle(PDBorderStyleDictionary bs)
{
this.getCOSObject().setItem(COSName.BS, bs);
}
/**
* This will retrieve the border style dictionary, specifying the width and dash pattern used in
* drawing the line.
*
* @return the border style dictionary.
*/
public PDBorderStyleDictionary getBorderStyle()
{
COSDictionary bs = this.getCOSObject().getDictionaryObject(COSName.BS, COSDictionary.class);
if (bs != null)
{
return new PDBorderStyleDictionary(bs);
}
return null;
}
/**
* This will set the line ending style.
*
* @param style The new style.
*/
public final void setLineEndingStyle(String style)
{
getCOSObject().setName(COSName.LE, style);
}
/**
* This will retrieve the line ending style.
*
* @return The line ending style, possible values shown in the LE_ constants section, LE_NONE if
* missing, never null.
*/
public String getLineEndingStyle()
{
return getCOSObject().getNameAsString(COSName.LE, PDAnnotationLine.LE_NONE);
}
// 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);
}
return null;
}
/**
* 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 array)
{
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) base2).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.
*/
public String getDefaultAppearance()
{
return getCOSObject().getString(COSName.DA);
}
/**
* Set the default appearance.
*
* @param daValue a string describing the default appearance.
*/
public void setDefaultAppearance(String daValue)
{
getCOSObject().setString(COSName.DA, daValue);
}
/**
* Get the default style string.
*
* The default style string defines the default style for rich text fields.
*
* @return the DS element of the dictionary object
*/
public String getDefaultStyleString()
{
return getCOSObject().getString(COSName.DS);
}
/**
* Set the default style string.
*
* Providing null as the value will remove the default style string.
*
* @param defaultStyleString a string describing the default style.
*/
public void setDefaultStyleString(String defaultStyleString)
{
getCOSObject().setString(COSName.DS, defaultStyleString);
}
/**
* This will get the 'quadding' or justification of the text to be displayed.
0 - Left
* (default)
1 - Centered
2 - Right
Please see the QUADDING_CONSTANTS in {@link
* PDVariableText }.
*
* @return The justification of the text strings.
*/
public int getQ()
{
return getCOSObject().getInt(COSName.Q, 0);
}
/**
* This will set the quadding/justification of the text. Please see the QUADDING_CONSTANTS in
* {@link PDVariableText }.
*
* @param q The new text justification.
*/
public void setQ(int q)
{
getCOSObject().setInt(COSName.Q, q);
}
/**
* This will set the rectangle difference rectangle. Giving the difference between the
* annotations rectangle and where the drawing occurs. (To take account of any effects applied
* through the BE entry for example)
*
* @param rd the rectangle difference
*/
public void setRectDifference(PDRectangle rd)
{
getCOSObject().setItem(COSName.RD, rd);
}
/**
* This will get the rectangle difference rectangle. Giving the difference between the
* annotations rectangle and where the drawing occurs. (To take account of any effects applied
* through the BE entry for example)
*
* @return the rectangle difference
*/
public PDRectangle getRectDifference()
{
COSBase base = getCOSObject().getDictionaryObject(COSName.RD);
if (base instanceof COSArray)
{
return new PDRectangle((COSArray) base);
}
return null;
}
/**
* This will set the difference between the annotations "outer" rectangle defined by /Rect and
* boundaries of the underlying.
*
*
* This will set an equal difference for all sides
*
*
* @param difference from the annotations /Rect entry
*/
public void setRectDifferences(float difference)
{
setRectDifferences(difference, difference, difference, difference);
}
/**
* This will set the difference between the annotations "outer" rectangle defined by /Rect and
* the border.
*
* @param differenceLeft left difference from the annotations /Rect entry
* @param differenceTop top difference from the annotations /Rect entry
* @param differenceRight right difference from the annotations /Rect entry
* @param differenceBottom bottom difference from the annotations /Rect entry
*/
public void setRectDifferences(float differenceLeft, float differenceTop, float differenceRight,
float differenceBottom)
{
COSArray margins = new COSArray();
margins.add(new COSFloat(differenceLeft));
margins.add(new COSFloat(differenceTop));
margins.add(new COSFloat(differenceRight));
margins.add(new COSFloat(differenceBottom));
getCOSObject().setItem(COSName.RD, margins);
}
/**
* This will get the margin between the annotations "outer" rectangle defined by /Rect and the
* boundaries of the underlying caret.
*
* @return the differences. If the entry hasn't been set am empty array is returned.
*/
public float[] getRectDifferences()
{
COSBase margin = getCOSObject().getItem(COSName.RD);
if (margin instanceof COSArray)
{
return ((COSArray) margin).toFloatArray();
}
return new float[] {};
}
/**
* This will set the coordinates of the callout line. (PDF 1.6 and higher) Only relevant if the
* intent is FreeTextCallout.
*
* @param callout An array of four or six numbers specifying a callout line attached to the free
* text annotation. Six numbers [ x1 y1 x2 y2 x3 y3 ] represent the starting,
* knee point, and ending coordinates of the line in default user space, four
* numbers [ x1 y1 x2 y2 ] represent the starting and ending coordinates of the
* line.
*/
public final void setCallout(float[] callout)
{
COSArray newCallout = new COSArray();
newCallout.setFloatArray(callout);
getCOSObject().setItem(COSName.CL, newCallout);
}
/**
* This will get the coordinates of the callout line. (PDF 1.6 and higher) Only relevant if the
* intent is FreeTextCallout.
*
* @return An array of four or six numbers specifying a callout line attached to the free text
* annotation. Six numbers [ x1 y1 x2 y2 x3 y3 ] represent the starting, knee point, and ending
* coordinates of the line in default user space, four numbers [ x1 y1 x2 y2 ] represent the
* starting and ending coordinates of the line.
*/
public float[] getCallout()
{
COSBase base = getCOSObject().getDictionaryObject(COSName.CL);
if (base instanceof COSArray)
{
return ((COSArray) base).toFloatArray();
}
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.set(0, COSName.getPDFName(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.set(1, COSName.getPDFName(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 array)
{
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) base2).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)
{
PDAppearanceHandler appearanceHandler = null;
if (SUB_TYPE_CARET.equals(getSubtype()))
{
appearanceHandler = new PDCaretAppearanceHandler(this);
}
else if (SUB_TYPE_FREETEXT.equals(getSubtype()))
{
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);
}
else if (SUB_TYPE_POLYLINE.equals(getSubtype()))
{
appearanceHandler = new PDPolylineAppearanceHandler(this);
}
else if (SUB_TYPE_SOUND.equals(getSubtype()))
{
appearanceHandler = new PDSoundAppearanceHandler(this);
}
else if (PDAnnotationFileAttachment.SUB_TYPE.equals(getSubtype()))
{
appearanceHandler = new PDFileAttachmentAppearanceHandler(this);
}
if (appearanceHandler != null)
{
appearanceHandler.generateAppearanceStreams();
}
}
else
{
customAppearanceHandler.generateAppearanceStreams();
}
}
}