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 2020/12/11 18:20:16 UTC

svn commit: r1884334 - in /pdfbox/branches/2.0/pdfbox/src: main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormGenerateAppearancesTest.java

Author: msahyoun
Date: Fri Dec 11 18:20:16 2020
New Revision: 1884334

URL: http://svn.apache.org/viewvc?rev=1884334&view=rev
Log:
PDFBOX-5041: calculate Capheight/Descent for missing font descriptor; avoid NPE

Added:
    pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormGenerateAppearancesTest.java
Modified:
    pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java

Modified: pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java?rev=1884334&r1=1884333&r2=1884334&view=diff
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java (original)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdmodel/interactive/form/AppearanceGeneratorHelper.java Fri Dec 11 18:20:16 2020
@@ -17,6 +17,7 @@
 package org.apache.pdfbox.pdmodel.interactive.form;
 
 import java.awt.geom.AffineTransform;
+import java.awt.geom.GeneralPath;
 import java.awt.geom.Point2D;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -25,6 +26,7 @@ import java.util.ArrayList;
 import java.util.List;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
+import org.apache.fontbox.util.BoundingBox;
 import org.apache.pdfbox.contentstream.operator.Operator;
 import org.apache.pdfbox.cos.COSName;
 import org.apache.pdfbox.cos.COSString;
@@ -34,6 +36,10 @@ import org.apache.pdfbox.pdmodel.PDResou
 import org.apache.pdfbox.pdmodel.PDPageContentStream;
 import org.apache.pdfbox.pdmodel.common.PDRectangle;
 import org.apache.pdfbox.pdmodel.font.PDFont;
+import org.apache.pdfbox.pdmodel.font.PDSimpleFont;
+import org.apache.pdfbox.pdmodel.font.PDType3CharProc;
+import org.apache.pdfbox.pdmodel.font.PDType3Font;
+import org.apache.pdfbox.pdmodel.font.PDVectorFont;
 import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
 import org.apache.pdfbox.pdmodel.interactive.action.PDActionJavaScript;
 import org.apache.pdfbox.pdmodel.interactive.action.PDFormFieldAdditionalActions;
@@ -51,32 +57,33 @@ import org.apache.pdfbox.util.Matrix;
  * @author Stephan Gerhard
  * @author Ben Litchfield
  */
-class AppearanceGeneratorHelper
-{
+class AppearanceGeneratorHelper {
     private static final Log LOG = LogFactory.getLog(AppearanceGeneratorHelper.class);
 
     private static final Operator BMC = Operator.getOperator("BMC");
     private static final Operator EMC = Operator.getOperator("EMC");
- 
+
     private final PDVariableText field;
-    
+
     private PDDefaultAppearanceString defaultAppearance;
     private String value;
-    
+
     /**
      * The highlight color
      *
-     * The color setting is used by Adobe to display the highlight box for selected entries in a list box.
+     * The color setting is used by Adobe to display the highlight box for selected
+     * entries in a list box.
      *
-     * Regardless of other settings in an existing appearance stream Adobe will always use this value.
+     * Regardless of other settings in an existing appearance stream Adobe will
+     * always use this value.
      */
-    private static final float[] HIGHLIGHT_COLOR = {153/255f, 193/255f, 215/255f};
- 
+    private static final float[] HIGHLIGHT_COLOR = { 153 / 255f, 193 / 255f, 215 / 255f };
+
     /**
      * The scaling factor for font units to PDF units
      */
     private static final int FONTSCALE = 1000;
-    
+
     /**
      * The default font size used for multiline text
      */
@@ -87,197 +94,168 @@ class AppearanceGeneratorHelper
      */
     private static final float MINIMUM_FONT_SIZE = 4;
     private static final float MAXIMUM_FONT_SIZE = 300;
-    
+
     /**
      * The default padding applied by Acrobat to the fields bbox.
      */
     private static final float DEFAULT_PADDING = 0.5f;
-    
+
     /**
      * Constructs a COSAppearance from the given field.
      *
      * @param field the field which you wish to control the appearance of
-     * @throws IOException 
+     * @throws IOException
      */
-    AppearanceGeneratorHelper(PDVariableText field) throws IOException
-    {
+    AppearanceGeneratorHelper(PDVariableText field) throws IOException {
         this.field = field;
         validateAndEnsureAcroFormResources();
-        
-        try
-        {
+
+        try {
             this.defaultAppearance = field.getDefaultAppearanceString();
-        }
-        catch (IOException ex)
-        {
-            throw new IOException("Could not process default appearance string '" +
-                                   field.getDefaultAppearance() + "' for field '" +
-                                   field.getFullyQualifiedName() + "'", ex);
+        } catch (IOException ex) {
+            throw new IOException("Could not process default appearance string '" + field.getDefaultAppearance()
+                    + "' for field '" + field.getFullyQualifiedName() + "'", ex);
         }
     }
-    
+
     /*
      * Adobe Reader/Acrobat are adding resources which are at the field/widget level
-     * to the AcroForm level. 
+     * to the AcroForm level.
      */
-    private void validateAndEnsureAcroFormResources()
-    {
-        // add font resources which might be available at the field 
+    private void validateAndEnsureAcroFormResources() {
+        // add font resources which might be available at the field
         // level but are not at the AcroForm level to the AcroForm
-        // to match Adobe Reader/Acrobat behavior        
-        if (field.getAcroForm().getDefaultResources() == null)
-        {
+        // to match Adobe Reader/Acrobat behavior
+        if (field.getAcroForm().getDefaultResources() == null) {
             return;
         }
-        
+
         PDResources acroFormResources = field.getAcroForm().getDefaultResources();
-        
-        for (PDAnnotationWidget widget : field.getWidgets())
-        {
-            if (widget.getNormalAppearanceStream() != null && widget.getNormalAppearanceStream().getResources() != null)
-            {
+
+        for (PDAnnotationWidget widget : field.getWidgets()) {
+            if (widget.getNormalAppearanceStream() != null
+                    && widget.getNormalAppearanceStream().getResources() != null) {
                 PDResources widgetResources = widget.getNormalAppearanceStream().getResources();
-                for (COSName fontResourceName : widgetResources.getFontNames())
-                {
-                    try
-                    {
-                        if (acroFormResources.getFont(fontResourceName) == null)
-                        {
+                for (COSName fontResourceName : widgetResources.getFontNames()) {
+                    try {
+                        if (acroFormResources.getFont(fontResourceName) == null) {
                             LOG.debug("Adding font resource " + fontResourceName + " from widget to AcroForm");
                             acroFormResources.put(fontResourceName, widgetResources.getFont(fontResourceName));
                         }
-                    }
-                    catch (IOException e)
-                    {
+                    } catch (IOException e) {
                         LOG.warn("Unable to match field level font with AcroForm font");
                     }
                 }
             }
         }
     }
-    
+
     /**
      * This is the public method for setting the appearance stream.
      *
      * @param apValue the String value which the appearance should represent
      * @throws IOException If there is an error creating the stream.
      */
-    public void setAppearanceValue(String apValue) throws IOException
-    {
+    public void setAppearanceValue(String apValue) throws IOException {
         value = getFormattedValue(apValue);
-        
+
         // Treat multiline field values in single lines as single lime values.
         // This is in line with how Adobe Reader behaves when enetring text
         // interactively but NOT how it behaves when the field value has been
         // set programmatically and Reader is forced to generate the appearance
         // using PDAcroForm.setNeedAppearances
         // see PDFBOX-3911
-        if (field instanceof PDTextField && !((PDTextField) field).isMultiline())
-        {
+        if (field instanceof PDTextField && !((PDTextField) field).isMultiline()) {
             value = apValue.replaceAll("\\u000D\\u000A|[\\u000A\\u000B\\u000C\\u000D\\u0085\\u2028\\u2029]", " ");
         }
 
-        for (PDAnnotationWidget widget : field.getWidgets())
-        {
-            // some fields have the /Da at the widget level if the 
+        for (PDAnnotationWidget widget : field.getWidgets()) {
+            // some fields have the /Da at the widget level if the
             // widgets differ in layout.
             PDDefaultAppearanceString acroFormAppearance = defaultAppearance;
-            
-            if (widget.getCOSObject().getDictionaryObject(COSName.DA) != null)
-            {
+
+            if (widget.getCOSObject().getDictionaryObject(COSName.DA) != null) {
                 defaultAppearance = getWidgetDefaultAppearanceString(widget);
             }
 
             PDRectangle rect = widget.getRectangle();
-            if (rect == null)
-            {
+            if (rect == null) {
                 widget.getCOSObject().removeItem(COSName.AP);
-                LOG.warn("widget of field " + field.getFullyQualifiedName() + " has no rectangle, no appearance stream created");
+                LOG.warn("widget of field " + field.getFullyQualifiedName()
+                        + " has no rectangle, no appearance stream created");
                 continue;
             }
 
             PDAppearanceDictionary appearanceDict = widget.getAppearance();
-            if (appearanceDict == null)
-            {
+            if (appearanceDict == null) {
                 appearanceDict = new PDAppearanceDictionary();
                 widget.setAppearance(appearanceDict);
             }
 
             PDAppearanceEntry appearance = appearanceDict.getNormalAppearance();
             // TODO support appearances other than "normal"
-                
+
             PDAppearanceStream appearanceStream;
-            if (isValidAppearanceStream(appearance))
-            {
+            if (isValidAppearanceStream(appearance)) {
                 appearanceStream = appearance.getAppearanceStream();
-            }
-            else
-            {
+            } else {
                 appearanceStream = prepareNormalAppearanceStream(widget);
                 appearanceDict.setNormalAppearance(appearanceStream);
                 // TODO support appearances other than "normal"
             }
-                
+
             /*
-             * Adobe Acrobat always recreates the complete appearance stream if there is an appearance characteristics
-             * entry (the widget dictionaries MK entry). In addition if there is no content yet also create the appearance
-             * stream from the entries.
+             * Adobe Acrobat always recreates the complete appearance stream if there is an
+             * appearance characteristics entry (the widget dictionaries MK entry). In
+             * addition if there is no content yet also create the appearance stream from
+             * the entries.
              * 
              */
-            if (widget.getAppearanceCharacteristics() != null || appearanceStream.getContentStream().getLength() == 0)
-            {
+            if (widget.getAppearanceCharacteristics() != null || appearanceStream.getContentStream().getLength() == 0) {
                 initializeAppearanceContent(widget, appearanceStream);
             }
-                
+
             setAppearanceContent(widget, appearanceStream);
-            
+
             // restore the field level appearance
-            defaultAppearance =  acroFormAppearance;
+            defaultAppearance = acroFormAppearance;
         }
     }
 
-    private String getFormattedValue(String apValue)
-    {
-        // format the field value for the appearance if there is scripting support and the field
+    private String getFormattedValue(String apValue) {
+        // format the field value for the appearance if there is scripting support and
+        // the field
         // has a format event
         PDFormFieldAdditionalActions actions = field.getActions();
 
-        if (actions != null && actions.getF() != null)
-        {
-            if (field.getAcroForm().getScriptingHandler() != null)
-            {
+        if (actions != null && actions.getF() != null) {
+            if (field.getAcroForm().getScriptingHandler() != null) {
                 ScriptingHandler scriptingHandler = field.getAcroForm().getScriptingHandler();
                 return scriptingHandler.format((PDActionJavaScript) field.getActions().getF(), apValue);
-            }
-            else
-            {
-                LOG.info("Field contains a formatting action but no SriptingHandler has been supplied - formatted value might be incorrect");
+            } else {
+                LOG.info(
+                        "Field contains a formatting action but no SriptingHandler has been supplied - formatted value might be incorrect");
                 return apValue;
             }
         }
         return apValue;
     }
 
-    private static boolean isValidAppearanceStream(PDAppearanceEntry appearance)
-    {
-        if (appearance == null)
-        {
+    private static boolean isValidAppearanceStream(PDAppearanceEntry appearance) {
+        if (appearance == null) {
             return false;
         }
-        if (!appearance.isStream())
-        {
+        if (!appearance.isStream()) {
             return false;
         }
         PDRectangle bbox = appearance.getAppearanceStream().getBBox();
-        if (bbox == null)
-        {
+        if (bbox == null) {
             return false;
         }
         return Math.abs(bbox.getWidth()) > 0 && Math.abs(bbox.getHeight()) > 0;
     }
 
-    private PDAppearanceStream prepareNormalAppearanceStream(PDAnnotationWidget widget)
-    {
+    private PDAppearanceStream prepareNormalAppearanceStream(PDAnnotationWidget widget) {
         PDAppearanceStream appearanceStream = new PDAppearanceStream(field.getAcroForm().getDocument());
 
         // Calculate the entries for the bounding box and the transformation matrix
@@ -291,8 +269,7 @@ class AppearanceGeneratorHelper
         appearanceStream.setBBox(bbox);
 
         AffineTransform at = calculateMatrix(bbox, rotation);
-        if (!at.isIdentity())
-        {
+        if (!at.isIdentity()) {
             appearanceStream.setMatrix(at);
         }
         appearanceStream.setFormType(1);
@@ -300,90 +277,80 @@ class AppearanceGeneratorHelper
         return appearanceStream;
     }
 
-    private PDDefaultAppearanceString getWidgetDefaultAppearanceString(PDAnnotationWidget widget) throws IOException
-    {
+    private PDDefaultAppearanceString getWidgetDefaultAppearanceString(PDAnnotationWidget widget) throws IOException {
         COSString da = (COSString) widget.getCOSObject().getDictionaryObject(COSName.DA);
         PDResources dr = field.getAcroForm().getDefaultResources();
         return new PDDefaultAppearanceString(da, dr);
     }
 
-    private int resolveRotation(PDAnnotationWidget widget)
-    {
-        PDAppearanceCharacteristicsDictionary  characteristicsDictionary = widget.getAppearanceCharacteristics();
-        if (characteristicsDictionary != null)
-        {
+    private int resolveRotation(PDAnnotationWidget widget) {
+        PDAppearanceCharacteristicsDictionary characteristicsDictionary = widget.getAppearanceCharacteristics();
+        if (characteristicsDictionary != null) {
             // 0 is the default value if the R key doesn't exist
             return characteristicsDictionary.getRotation();
         }
         return 0;
     }
 
-
     /**
      * Initialize the content of the appearance stream.
      * 
-     * Get settings like border style, border width and colors to be used to draw a rectangle and background color 
-     * around the widget
+     * Get settings like border style, border width and colors to be used to draw a
+     * rectangle and background color around the widget
      * 
-     * @param widget the field widget
+     * @param widget           the field widget
      * @param appearanceStream the appearance stream to be used
      * @throws IOException in case we can't write to the appearance stream
      */
-    private void initializeAppearanceContent(PDAnnotationWidget widget, PDAppearanceStream appearanceStream) throws IOException
-    {
+    private void initializeAppearanceContent(PDAnnotationWidget widget, PDAppearanceStream appearanceStream)
+            throws IOException {
         ByteArrayOutputStream output = new ByteArrayOutputStream();
-        PDPageContentStream contents = new PDPageContentStream(field.getAcroForm().getDocument(),
-                appearanceStream, output);
+        PDPageContentStream contents = new PDPageContentStream(field.getAcroForm().getDocument(), appearanceStream,
+                output);
         PDAppearanceCharacteristicsDictionary appearanceCharacteristics = widget.getAppearanceCharacteristics();
-        
+
         // TODO: support more entries like patterns, etc.
-        if (appearanceCharacteristics != null)
-        {
+        if (appearanceCharacteristics != null) {
             PDColor backgroundColour = appearanceCharacteristics.getBackground();
-            if (backgroundColour != null)
-            {
+            if (backgroundColour != null) {
                 contents.setNonStrokingColor(backgroundColour);
                 PDRectangle bbox = resolveBoundingBox(widget, appearanceStream);
-                contents.addRect(bbox.getLowerLeftX(),bbox.getLowerLeftY(),bbox.getWidth(), bbox.getHeight());
+                contents.addRect(bbox.getLowerLeftX(), bbox.getLowerLeftY(), bbox.getWidth(), bbox.getHeight());
                 contents.fill();
             }
 
             float lineWidth = 0f;
             PDColor borderColour = appearanceCharacteristics.getBorderColour();
-            if (borderColour != null)
-            {
+            if (borderColour != null) {
                 contents.setStrokingColor(borderColour);
                 lineWidth = 1f;
             }
             PDBorderStyleDictionary borderStyle = widget.getBorderStyle();
-            if (borderStyle != null && borderStyle.getWidth() > 0)
-            {
+            if (borderStyle != null && borderStyle.getWidth() > 0) {
                 lineWidth = borderStyle.getWidth();
             }
 
-            if (lineWidth > 0 && borderColour != null)
-            {
-                if (lineWidth != 1)
-                {
+            if (lineWidth > 0 && borderColour != null) {
+                if (lineWidth != 1) {
                     contents.setLineWidth(lineWidth);
                 }
                 PDRectangle bbox = resolveBoundingBox(widget, appearanceStream);
-                PDRectangle clipRect = applyPadding(bbox, Math.max(DEFAULT_PADDING, lineWidth/2)); 
-                contents.addRect(clipRect.getLowerLeftX(),clipRect.getLowerLeftY(),clipRect.getWidth(), clipRect.getHeight());
+                PDRectangle clipRect = applyPadding(bbox, Math.max(DEFAULT_PADDING, lineWidth / 2));
+                contents.addRect(clipRect.getLowerLeftX(), clipRect.getLowerLeftY(), clipRect.getWidth(),
+                        clipRect.getHeight());
                 contents.closeAndStroke();
             }
         }
-        
+
         contents.close();
         output.close();
         writeToStream(output.toByteArray(), appearanceStream);
     }
-    
+
     /**
      * Parses an appearance stream into tokens.
      */
-    private List<Object> tokenize(PDAppearanceStream appearanceStream) throws IOException
-    {
+    private List<Object> tokenize(PDAppearanceStream appearanceStream) throws IOException {
         PDFStreamParser parser = new PDFStreamParser(appearanceStream);
         parser.parse();
         return parser.getTokens();
@@ -392,43 +359,36 @@ class AppearanceGeneratorHelper
     /**
      * Constructs and sets new contents for given appearance stream.
      */
-    private void setAppearanceContent(PDAnnotationWidget widget,
-                                      PDAppearanceStream appearanceStream) throws IOException
-    {
+    private void setAppearanceContent(PDAnnotationWidget widget, PDAppearanceStream appearanceStream)
+            throws IOException {
         // first copy any needed resources from the document’s DR dictionary into
         // the stream’s Resources dictionary
         defaultAppearance.copyNeededResourcesTo(appearanceStream);
-        
+
         // then replace the existing contents of the appearance stream from /Tx BMC
         // to the matching EMC
         ByteArrayOutputStream output = new ByteArrayOutputStream();
         ContentStreamWriter writer = new ContentStreamWriter(output);
-        
+
         List<Object> tokens = tokenize(appearanceStream);
         int bmcIndex = tokens.indexOf(BMC);
-        if (bmcIndex == -1)
-        {
+        if (bmcIndex == -1) {
             // append to existing stream
             writer.writeTokens(tokens);
             writer.writeTokens(COSName.TX, BMC);
-        }
-        else
-        {
+        } else {
             // prepend content before BMC
             writer.writeTokens(tokens.subList(0, bmcIndex + 1));
         }
-        
+
         // insert field contents
         insertGeneratedAppearance(widget, appearanceStream, output);
-        
+
         int emcIndex = tokens.indexOf(EMC);
-        if (emcIndex == -1)
-        {
+        if (emcIndex == -1) {
             // append EMC
             writer.writeTokens(EMC);
-        }
-        else
-        {
+        } else {
             // append contents after EMC
             writer.writeTokens(tokens.subList(emcIndex, tokens.size()));
         }
@@ -436,68 +396,61 @@ class AppearanceGeneratorHelper
         output.close();
         writeToStream(output.toByteArray(), appearanceStream);
     }
-    
+
     /**
-     * Generate and insert text content and clipping around it.   
+     * Generate and insert text content and clipping around it.
      */
-    private void insertGeneratedAppearance(PDAnnotationWidget widget,
-                                           PDAppearanceStream appearanceStream,
-                                           OutputStream output) throws IOException
-    {
-        PDPageContentStream contents = new PDPageContentStream(field.getAcroForm().getDocument(),
-                                                               appearanceStream, output);
+    private void insertGeneratedAppearance(PDAnnotationWidget widget, PDAppearanceStream appearanceStream,
+            OutputStream output) throws IOException {
+        PDPageContentStream contents = new PDPageContentStream(field.getAcroForm().getDocument(), appearanceStream,
+                output);
 
         PDRectangle bbox = resolveBoundingBox(widget, appearanceStream);
 
-        // Acrobat calculates the left and right padding dependent on the offset of the border edge
+        // Acrobat calculates the left and right padding dependent on the offset of the
+        // border edge
         // This calculation works for forms having been generated by Acrobat.
-        // The minimum distance is always 1f even if there is no rectangle being drawn around.
+        // The minimum distance is always 1f even if there is no rectangle being drawn
+        // around.
         float borderWidth = 0;
-        if (widget.getBorderStyle() != null)
-        {
+        if (widget.getBorderStyle() != null) {
             borderWidth = widget.getBorderStyle().getWidth();
         }
         PDRectangle clipRect = applyPadding(bbox, Math.max(1f, borderWidth));
         PDRectangle contentRect = applyPadding(clipRect, Math.max(1f, borderWidth));
-        
+
         contents.saveGraphicsState();
-        
+
         // Acrobat always adds a clipping path
-        contents.addRect(clipRect.getLowerLeftX(), clipRect.getLowerLeftY(),
-                clipRect.getWidth(), clipRect.getHeight());
+        contents.addRect(clipRect.getLowerLeftX(), clipRect.getLowerLeftY(), clipRect.getWidth(), clipRect.getHeight());
         contents.clip();
-        
+
         // get the font
         PDFont font = defaultAppearance.getFont();
-        if (font == null)
-        {
+        if (font == null) {
             throw new IllegalArgumentException("font is null, check whether /DA entry is incomplete or incorrect");
         }
-        if (font.getName().contains("+"))
-        {
-            LOG.warn("Font '" + defaultAppearance.getFontName().getName() +
-                     "' of field '" + field.getFullyQualifiedName() + 
-                     "' contains subsetted font '" + font.getName() + "'");
-            LOG.warn("This may bring trouble with PDField.setValue(), PDAcroForm.flatten() or " +
-                     "PDAcroForm.refreshAppearances()");
+        if (font.getName().contains("+")) {
+            LOG.warn("Font '" + defaultAppearance.getFontName().getName() + "' of field '"
+                    + field.getFullyQualifiedName() + "' contains subsetted font '" + font.getName() + "'");
+            LOG.warn("This may bring trouble with PDField.setValue(), PDAcroForm.flatten() or "
+                    + "PDAcroForm.refreshAppearances()");
             LOG.warn("You should replace this font with a non-subsetted font:");
             LOG.warn("PDFont font = PDType0Font.load(doc, new FileInputStream(fontfile), false);");
-            LOG.warn("acroForm.getDefaultResources().put(COSName.getPDFName(\"" +
-                     defaultAppearance.getFontName().getName() + "\", font);");
+            LOG.warn("acroForm.getDefaultResources().put(COSName.getPDFName(\""
+                    + defaultAppearance.getFontName().getName() + "\", font);");
         }
-        
+
         // calculate the fontSize (because 0 = autosize)
         float fontSize = defaultAppearance.getFontSize();
-        
-        if (fontSize == 0)
-        {
-            fontSize = calculateFontSize(font, contentRect);            
+
+        if (fontSize == 0) {
+            fontSize = calculateFontSize(font, contentRect);
         }
-        
+
         // for a listbox generate the highlight rectangle for the selected
         // options
-        if (field instanceof PDListBox)
-        {
+        if (field instanceof PDListBox) {
             insertGeneratedListboxSelectionHighlight(contents, appearanceStream, font, fontSize);
         }
 
@@ -513,97 +466,89 @@ class AppearanceGeneratorHelper
         // calculate font metrics at font size
         float fontScaleY = fontSize / FONTSCALE;
         float fontBoundingBoxAtSize = font.getBoundingBox().getHeight() * fontScaleY;
-        float fontCapAtSize = font.getFontDescriptor().getCapHeight() * fontScaleY;
-        float fontDescentAtSize = font.getFontDescriptor().getDescent() * fontScaleY;
-        
-        if (field instanceof PDTextField && ((PDTextField) field).isMultiline())
-        {
-            y = contentRect.getUpperRightY() - fontBoundingBoxAtSize;
+
+        float fontCapAtSize = 0;
+        float fontDescentAtSize = 0;
+
+        if (font.getFontDescriptor() != null) {
+            fontCapAtSize = font.getFontDescriptor().getCapHeight() * fontScaleY;
+            fontDescentAtSize = font.getFontDescriptor().getDescent() * fontScaleY;
+        } else {
+            float fontCapHeight = resolveCapHeight(font);
+            float fontDescent = resolveDescent(font);
+            LOG.debug("missing font descriptor - resolved Cap/Descent to " + fontCapHeight + "/" + fontDescent);
+            fontCapAtSize = fontCapHeight * fontScaleY;
+            fontDescentAtSize = fontDescent * fontScaleY;
         }
-        else
-        {
-            // Adobe shows the text 'shiftet up' in case the caps don't fit into the clipping area
-            if (fontCapAtSize > clipRect.getHeight())
-            {
+
+        if (field instanceof PDTextField && ((PDTextField) field).isMultiline()) {
+            y = contentRect.getUpperRightY() - fontBoundingBoxAtSize;
+        } else {
+            // Adobe shows the text 'shiftet up' in case the caps don't fit into the
+            // clipping area
+            if (fontCapAtSize > clipRect.getHeight()) {
                 y = clipRect.getLowerLeftY() + -fontDescentAtSize;
-            }
-            else
-            {
+            } else {
                 // calculate the position based on the content rectangle
                 y = clipRect.getLowerLeftY() + (clipRect.getHeight() - fontCapAtSize) / 2;
-    
+
                 // check to ensure that ascents and descents fit
                 if (y - clipRect.getLowerLeftY() < -fontDescentAtSize) {
-    
+
                     float fontDescentBased = -fontDescentAtSize + contentRect.getLowerLeftY();
                     float fontCapBased = contentRect.getHeight() - contentRect.getLowerLeftY() - fontCapAtSize;
-    
+
                     y = Math.min(fontDescentBased, Math.max(y, fontCapBased));
                 }
             }
         }
-        
+
         // show the text
         float x = contentRect.getLowerLeftX();
-        
+
         // special handling for comb boxes as these are like table cells with individual
         // chars
-        if (shallComb())
-        {
+        if (shallComb()) {
             insertGeneratedCombAppearance(contents, appearanceStream, font, fontSize);
-        }
-        else if (field instanceof PDListBox)
-        {
+        } else if (field instanceof PDListBox) {
             insertGeneratedListboxAppearance(contents, appearanceStream, contentRect, font, fontSize);
-        }
-        else
-        {         
+        } else {
             PlainText textContent = new PlainText(value);
             AppearanceStyle appearanceStyle = new AppearanceStyle();
             appearanceStyle.setFont(font);
             appearanceStyle.setFontSize(fontSize);
-            
+
             // Adobe Acrobat uses the font's bounding box for the leading between the lines
             appearanceStyle.setLeading(font.getBoundingBox().getHeight() * fontScaleY);
-            
-            PlainTextFormatter formatter = new PlainTextFormatter
-                                                .Builder(contents)
-                                                    .style(appearanceStyle)
-                                                    .text(textContent)
-                                                    .width(contentRect.getWidth())
-                                                    .wrapLines(isMultiLine())
-                                                    .initialOffset(x, y)
-                                                    .textAlign(getTextAlign(widget))
-                                                    .build();
+
+            PlainTextFormatter formatter = new PlainTextFormatter.Builder(contents).style(appearanceStyle)
+                    .text(textContent).width(contentRect.getWidth()).wrapLines(isMultiLine()).initialOffset(x, y)
+                    .textAlign(getTextAlign(widget)).build();
             formatter.format();
         }
-    
+
         contents.endText();
         contents.restoreGraphicsState();
         contents.close();
     }
 
     /*
-     * PDFBox handles a widget with a joined in field dictionary and without
-     * an individual name as a widget only. As a result - as a widget can't have a
+     * PDFBox handles a widget with a joined in field dictionary and without an
+     * individual name as a widget only. As a result - as a widget can't have a
      * quadding /Q entry we need to do a low level access to the dictionary and
      * otherwise get the quadding from the field.
      */
-    private int getTextAlign(PDAnnotationWidget widget)
-    {
+    private int getTextAlign(PDAnnotationWidget widget) {
         // Use quadding value from joined field/widget if set, else use from field.
         return widget.getCOSObject().getInt(COSName.Q, field.getQ());
     }
 
-    private AffineTransform calculateMatrix(PDRectangle bbox, int rotation)
-    {
-        if (rotation == 0)
-        {
+    private AffineTransform calculateMatrix(PDRectangle bbox, int rotation) {
+        if (rotation == 0) {
             return new AffineTransform();
         }
         float tx = 0, ty = 0;
-        switch (rotation)
-        {
+        switch (rotation) {
             case 90:
                 tx = bbox.getUpperRightY();
                 break;
@@ -622,142 +567,125 @@ class AppearanceGeneratorHelper
 
     }
 
-    
-    private boolean isMultiLine()
-    {
+    private boolean isMultiLine() {
         return field instanceof PDTextField && ((PDTextField) field).isMultiline();
     }
-    
+
     /**
      * Determine if the appearance shall provide a comb output.
      * 
      * <p>
      * May be set only if the MaxLen entry is present in the text field dictionary
-     * and if the Multiline, Password, and FileSelect flags are clear.
-     * If set, the field shall be automatically divided into as many equally spaced positions,
+     * and if the Multiline, Password, and FileSelect flags are clear. If set, the
+     * field shall be automatically divided into as many equally spaced positions,
      * or combs, as the value of MaxLen, and the text is laid out into those combs.
      * </p>
      * 
      * @return the comb state
      */
-    private boolean shallComb()
-    {
-        return field instanceof PDTextField &&
-                ((PDTextField) field).isComb() &&
-                !((PDTextField) field).isMultiline() &&
-                !((PDTextField) field).isPassword() &&
-                !((PDTextField) field).isFileSelect();           
+    private boolean shallComb() {
+        return field instanceof PDTextField && ((PDTextField) field).isComb() && !((PDTextField) field).isMultiline()
+                && !((PDTextField) field).isPassword() && !((PDTextField) field).isFileSelect();
     }
-    
+
     /**
      * Generate the appearance for comb fields.
      * 
-     * @param contents the content stream to write to
+     * @param contents         the content stream to write to
      * @param appearanceStream the appearance stream used
-     * @param font the font to be used
-     * @param fontSize the font size to be used
+     * @param font             the font to be used
+     * @param fontSize         the font size to be used
      * @throws IOException
      */
     private void insertGeneratedCombAppearance(PDPageContentStream contents, PDAppearanceStream appearanceStream,
-            PDFont font, float fontSize) throws IOException
-    {
-        
-        // TODO:    Currently the quadding is not taken into account
-        //          so the comb is always filled from left to right.
-        
+            PDFont font, float fontSize) throws IOException {
+
+        // TODO: Currently the quadding is not taken into account
+        // so the comb is always filled from left to right.
+
         int maxLen = ((PDTextField) field).getMaxLen();
         int numChars = Math.min(value.length(), maxLen);
-        
+
         PDRectangle paddingEdge = applyPadding(appearanceStream.getBBox(), 1);
-        
+
         float combWidth = appearanceStream.getBBox().getWidth() / maxLen;
         float ascentAtFontSize = font.getFontDescriptor().getAscent() / FONTSCALE * fontSize;
-        float baselineOffset = paddingEdge.getLowerLeftY() +  
-                (appearanceStream.getBBox().getHeight() - ascentAtFontSize)/2;
-        
+        float baselineOffset = paddingEdge.getLowerLeftY()
+                + (appearanceStream.getBBox().getHeight() - ascentAtFontSize) / 2;
+
         float prevCharWidth = 0f;
-        
+
         float xOffset = combWidth / 2;
 
-        for (int i = 0; i < numChars; i++) 
-        {
-            String combString = value.substring(i, i+1);
-            float currCharWidth = font.getStringWidth(combString) / FONTSCALE * fontSize/2;
-            
-            xOffset = xOffset + prevCharWidth/2 - currCharWidth/2;
-            
+        for (int i = 0; i < numChars; i++) {
+            String combString = value.substring(i, i + 1);
+            float currCharWidth = font.getStringWidth(combString) / FONTSCALE * fontSize / 2;
+
+            xOffset = xOffset + prevCharWidth / 2 - currCharWidth / 2;
+
             contents.newLineAtOffset(xOffset, baselineOffset);
             contents.showText(combString);
-            
+
             baselineOffset = 0;
             prevCharWidth = currCharWidth;
             xOffset = combWidth;
         }
     }
-    
-    private void insertGeneratedListboxSelectionHighlight(PDPageContentStream contents, PDAppearanceStream appearanceStream,
-            PDFont font, float fontSize) throws IOException
-    {
+
+    private void insertGeneratedListboxSelectionHighlight(PDPageContentStream contents,
+            PDAppearanceStream appearanceStream, PDFont font, float fontSize) throws IOException {
         List<Integer> indexEntries = ((PDListBox) field).getSelectedOptionsIndex();
         List<String> values = ((PDListBox) field).getValue();
         List<String> options = ((PDListBox) field).getOptionsExportValues();
-        
-        if (!values.isEmpty() && !options.isEmpty() && indexEntries.isEmpty())
-        {
+
+        if (!values.isEmpty() && !options.isEmpty() && indexEntries.isEmpty()) {
             // create indexEntries from options
             indexEntries = new ArrayList<Integer>();
-            for (String v : values)
-            {
+            for (String v : values) {
                 indexEntries.add(options.indexOf(v));
             }
         }
 
-        // The first entry which shall be presented might be adjusted by the optional TI key
-        // If this entry is present the first entry to be displayed is the keys value otherwise
+        // The first entry which shall be presented might be adjusted by the optional TI
+        // key
+        // If this entry is present the first entry to be displayed is the keys value
+        // otherwise
         // display starts with the first entry in Opt.
         int topIndex = ((PDListBox) field).getTopIndex();
-        
-        float highlightBoxHeight = font.getBoundingBox().getHeight() * fontSize / FONTSCALE;       
 
-        // the padding area 
+        float highlightBoxHeight = font.getBoundingBox().getHeight() * fontSize / FONTSCALE;
+
+        // the padding area
         PDRectangle paddingEdge = applyPadding(appearanceStream.getBBox(), 1);
 
-        for (int selectedIndex : indexEntries)
-        {
+        for (int selectedIndex : indexEntries) {
             contents.setNonStrokingColor(HIGHLIGHT_COLOR[0], HIGHLIGHT_COLOR[1], HIGHLIGHT_COLOR[2]);
 
             contents.addRect(paddingEdge.getLowerLeftX(),
                     paddingEdge.getUpperRightY() - highlightBoxHeight * (selectedIndex - topIndex + 1) + 2,
-                    paddingEdge.getWidth(),
-                    highlightBoxHeight);
+                    paddingEdge.getWidth(), highlightBoxHeight);
             contents.fill();
         }
         contents.setNonStrokingColor(0f);
     }
-    
-    
+
     private void insertGeneratedListboxAppearance(PDPageContentStream contents, PDAppearanceStream appearanceStream,
-            PDRectangle contentRect, PDFont font, float fontSize) throws IOException
-    {
+            PDRectangle contentRect, PDFont font, float fontSize) throws IOException {
         contents.setNonStrokingColor(0f);
-        
+
         int q = field.getQ();
 
-        if (q == PDVariableText.QUADDING_CENTERED || q == PDVariableText.QUADDING_RIGHT)
-        {
+        if (q == PDVariableText.QUADDING_CENTERED || q == PDVariableText.QUADDING_RIGHT) {
             float fieldWidth = appearanceStream.getBBox().getWidth();
             float stringWidth = (font.getStringWidth(value) / FONTSCALE) * fontSize;
             float adjustAmount = fieldWidth - stringWidth - 4;
 
-            if (q == PDVariableText.QUADDING_CENTERED)
-            {
+            if (q == PDVariableText.QUADDING_CENTERED) {
                 adjustAmount = adjustAmount / 2.0f;
             }
 
             contents.newLineAtOffset(adjustAmount, 0);
-        }
-        else if (q != PDVariableText.QUADDING_LEFT)
-        {
+        } else if (q != PDVariableText.QUADDING_LEFT) {
             throw new IOException("Error: Unknown justification value:" + q);
         }
 
@@ -767,16 +695,12 @@ class AppearanceGeneratorHelper
         float yTextPos = contentRect.getUpperRightY();
 
         int topIndex = ((PDListBox) field).getTopIndex();
-        
-        for (int i = topIndex; i < numOptions; i++)
-        {
-           
-            if (i == topIndex)
-            {
+
+        for (int i = topIndex; i < numOptions; i++) {
+
+            if (i == topIndex) {
                 yTextPos = yTextPos - font.getFontDescriptor().getAscent() / FONTSCALE * fontSize;
-            }
-            else
-            {
+            } else {
                 yTextPos = yTextPos - font.getBoundingBox().getHeight() / FONTSCALE * fontSize;
                 contents.beginText();
             }
@@ -784,52 +708,44 @@ class AppearanceGeneratorHelper
             contents.newLineAtOffset(contentRect.getLowerLeftX(), yTextPos);
             contents.showText(options.get(i));
 
-            if (i != (numOptions - 1))
-            {
+            if (i != (numOptions - 1)) {
                 contents.endText();
             }
         }
     }
-    
+
     /**
      * Writes the stream to the actual stream in the COSStream.
      *
      * @throws IOException If there is an error writing to the stream
      */
-    private void writeToStream(byte[] data, PDAppearanceStream appearanceStream) throws IOException
-    {
+    private void writeToStream(byte[] data, PDAppearanceStream appearanceStream) throws IOException {
         OutputStream out = appearanceStream.getCOSObject().createOutputStream();
         out.write(data);
         out.close();
     }
 
     /**
-     * My "not so great" method for calculating the fontsize. It does not work superb, but it
-     * handles ok.
+     * My "not so great" method for calculating the fontsize. It does not work
+     * superb, but it handles ok.
      * 
      * @return the calculated font-size
      * @throws IOException If there is an error getting the font information.
      */
-    private float calculateFontSize(PDFont font, PDRectangle contentRect) throws IOException
-    {
+    private float calculateFontSize(PDFont font, PDRectangle contentRect) throws IOException {
         float fontSize = defaultAppearance.getFontSize();
-        
+
         // zero is special, it means the text is auto-sized
-        if (fontSize == 0)
-        {
-            if (isMultiLine())
-            {
+        if (fontSize == 0) {
+            if (isMultiLine()) {
                 PlainText textContent = new PlainText(value);
-                if (textContent.getParagraphs() != null)
-                {
+                if (textContent.getParagraphs() != null) {
                     float width = contentRect.getWidth() - contentRect.getLowerLeftX();
                     float fs = MINIMUM_FONT_SIZE;
-                    while (fs <= DEFAULT_FONT_SIZE)
-                    {
+                    while (fs <= DEFAULT_FONT_SIZE) {
                         // determine the number of lines needed for this font and contentRect
                         int numLines = 0;
-                        for (PlainText.Paragraph paragraph : textContent.getParagraphs())
-                        {
+                        for (PlainText.Paragraph paragraph : textContent.getParagraphs()) {
                             numLines += paragraph.getLines(font, fs, width).size();
                         }
                         // calculate the height required for this font size
@@ -838,8 +754,7 @@ class AppearanceGeneratorHelper
                         float height = leading * numLines;
 
                         // if this font size didn't fit, use the prior size that did fit
-                        if (height > contentRect.getHeight())
-                        {
+                        if (height > contentRect.getHeight()) {
                             return Math.max(fs - 1, MINIMUM_FONT_SIZE);
                         }
                         fs++;
@@ -849,61 +764,113 @@ class AppearanceGeneratorHelper
 
                 // Acrobat defaults to 12 for multiline text with size 0
                 return DEFAULT_FONT_SIZE;
-            }
-            else
-            {
+            } else {
                 float yScalingFactor = FONTSCALE * font.getFontMatrix().getScaleY();
                 float xScalingFactor = FONTSCALE * font.getFontMatrix().getScaleX();
-                
+
                 // fit width
                 float width = font.getStringWidth(value) * font.getFontMatrix().getScaleX();
                 float widthBasedFontSize = contentRect.getWidth() / width * xScalingFactor;
 
                 // fit height
-                float height = (font.getFontDescriptor().getCapHeight() +
-                               -font.getFontDescriptor().getDescent()) * font.getFontMatrix().getScaleY();
-                if (height <= 0)
-                {
+                float height = (font.getFontDescriptor().getCapHeight() + -font.getFontDescriptor().getDescent())
+                        * font.getFontMatrix().getScaleY();
+                if (height <= 0) {
                     height = font.getBoundingBox().getHeight() * font.getFontMatrix().getScaleY();
                 }
 
                 float heightBasedFontSize = contentRect.getHeight() / height * yScalingFactor;
-                
+
                 return Math.min(heightBasedFontSize, widthBasedFontSize);
             }
         }
         return fontSize;
     }
-    
+
+    /*
+     * Resolve the cap height.
+     * 
+     * This is a very basic implementation using the height of "H" as reference.
+     */
+    private float resolveCapHeight(PDFont font) throws IOException {
+        return resolveGlyphHeight(font, "H".codePointAt(0));
+    }
+
+    /*
+     * Resolve the descent.
+     * 
+     * This is a very basic implementation using the height of "y" - "a" as reference.
+     */
+    private float resolveDescent(PDFont font) throws IOException {
+        return resolveGlyphHeight(font, "y".codePointAt(0)) - resolveGlyphHeight(font, "a".codePointAt(0));
+    }
+
+    // this calculates the real (except for type 3 fonts) individual glyph bounds
+    private float resolveGlyphHeight(PDFont font, int code) throws IOException {
+        GeneralPath path = null;
+        if (font instanceof PDType3Font) {
+            // It is difficult to calculate the real individual glyph bounds for type 3
+            // fonts
+            // because these are not vector fonts, the content stream could contain almost
+            // anything
+            // that is found in page content streams.
+            PDType3Font t3Font = (PDType3Font) font;
+            PDType3CharProc charProc = t3Font.getCharProc(code);
+            if (charProc != null) {
+                BoundingBox fontBBox = t3Font.getBoundingBox();
+                PDRectangle glyphBBox = charProc.getGlyphBBox();
+                if (glyphBBox != null) {
+                    // PDFBOX-3850: glyph bbox could be larger than the font bbox
+                    glyphBBox.setLowerLeftX(Math.max(fontBBox.getLowerLeftX(), glyphBBox.getLowerLeftX()));
+                    glyphBBox.setLowerLeftY(Math.max(fontBBox.getLowerLeftY(), glyphBBox.getLowerLeftY()));
+                    glyphBBox.setUpperRightX(Math.min(fontBBox.getUpperRightX(), glyphBBox.getUpperRightX()));
+                    glyphBBox.setUpperRightY(Math.min(fontBBox.getUpperRightY(), glyphBBox.getUpperRightY()));
+                    path = glyphBBox.toGeneralPath();
+                }
+            }
+        } else if (font instanceof PDVectorFont) {
+            PDVectorFont vectorFont = (PDVectorFont) font;
+            path = vectorFont.getPath(code);
+        } else if (font instanceof PDSimpleFont) {
+            PDSimpleFont simpleFont = (PDSimpleFont) font;
+
+            // these two lines do not always work, e.g. for the TT fonts in file 032431.pdf
+            // which is why PDVectorFont is tried first.
+            String name = simpleFont.getEncoding().getName(code);
+            path = simpleFont.getPath(name);
+        } else {
+            // shouldn't happen, please open issue in JIRA
+            LOG.warn("Unknown font class: " + font.getClass());
+        }
+        if (path == null) {
+            return -1;
+        }
+        return (float) path.getBounds2D().getHeight();
+    }
+
     /**
      * Resolve the bounding box.
      * 
-     * @param fieldWidget the annotation widget.
+     * @param fieldWidget      the annotation widget.
      * @param appearanceStream the annotations appearance stream.
      * @return the resolved boundingBox.
      */
-    private PDRectangle resolveBoundingBox(PDAnnotationWidget fieldWidget,
-                                           PDAppearanceStream appearanceStream)
-    {
+    private PDRectangle resolveBoundingBox(PDAnnotationWidget fieldWidget, PDAppearanceStream appearanceStream) {
         PDRectangle boundingBox = appearanceStream.getBBox();
-        if (boundingBox == null)
-        {
+        if (boundingBox == null) {
             boundingBox = fieldWidget.getRectangle().createRetranslatedRectangle();
         }
         return boundingBox;
     }
-    
+
     /**
      * Apply padding to a box.
      * 
      * @param box box
      * @return the padded box.
      */
-    private PDRectangle applyPadding(PDRectangle box, float padding)
-    {
-        return new PDRectangle(box.getLowerLeftX() + padding, 
-                               box.getLowerLeftY() + padding, 
-                               box.getWidth() - 2 * padding,
-                               box.getHeight() - 2 * padding);
+    private PDRectangle applyPadding(PDRectangle box, float padding) {
+        return new PDRectangle(box.getLowerLeftX() + padding, box.getLowerLeftY() + padding,
+                box.getWidth() - 2 * padding, box.getHeight() - 2 * padding);
     }
 }
\ No newline at end of file

Added: pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormGenerateAppearancesTest.java
URL: http://svn.apache.org/viewvc/pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormGenerateAppearancesTest.java?rev=1884334&view=auto
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormGenerateAppearancesTest.java (added)
+++ pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdmodel/interactive/form/PDAcroFormGenerateAppearancesTest.java Fri Dec 11 18:20:16 2020
@@ -0,0 +1,64 @@
+/*
+ * 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.form;
+
+import static org.junit.Assert.assertFalse;
+
+import java.io.IOException;
+import java.net.URL;
+
+import org.apache.pdfbox.io.IOUtils;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
+import org.junit.Test;
+
+public class PDAcroFormGenerateAppearancesTest {
+
+    /**
+     * PDFBOX-5041 Missing font descriptor
+     * 
+     * @throws IOException
+     */
+    @Test
+    public void test5041MissingFontDescriptor() throws IOException
+    {
+
+        String sourceUrl = "https://issues.apache.org/jira/secure/attachment/13016941/REDHAT-1301016-0.pdf";
+
+        PDDocument testPdf = null;
+        try
+        {
+            testPdf = PDDocument.load(new URL(sourceUrl).openStream());
+            PDDocumentCatalog catalog = testPdf.getDocumentCatalog();
+            boolean thrown = false;
+            try
+            {
+                catalog.getAcroForm();
+            }
+            catch (Exception e)
+            {
+                thrown = true;                
+            }
+            assertFalse("There shall be no exception when getting the AcroForm", thrown);
+        }
+        finally
+        {
+            IOUtils.closeQuietly(testPdf);
+        }
+    } 
+}