I know how Itext works and PDFBox does not work the same. In Itext the
result is that only the right fields are flattened. It seems there is a bug.

In PdfBox every field is flattened, even though I delivered a list of
fields, that were meant to be flattened.

It seems the PDF is destroyed by flattening.

I have attached the code.

On 25.05.21 19:27, Tilman Hausherr wrote:
> Here's a PDF flattened by itext
> https://github.com/itext/i7js-examples/blob/develop/cmpfiles/sandbox/acroforms/cmp_checkbox_flatten.pdf
>
> and they do something similar to what we do, i.e. remove the fields
> and converting it to form XObjects.
> Tilman
>
> Am 25.05.2021 um 19:03 schrieb [email protected]:
>> Am Dienstag, dem 25.05.2021 um 18:33 +0200 schrieb Ranjeet Kuruvilla:
>>> Hallo.
>>> It is clear, that flattening does have a bug. Compare flattening of
>>> PDFBox with IText and you realize, that PDFBox destroys all fields,
>>> once
>>> I call
>>>
>>> acroform.flatten(fields, true) or acroform.flatten(fields, false)
>>>
>>>
>> the purpose of flatten is that the flattened fields are removed and
>> become part of the regular page content.
>>
>> If you'd like to keep the field but would like it to be protected you
>> have to set the field to read only. Keep in mind that one could use a
>> lib and remove the flag and change the content afterwards.
>>
>> Now, if there is really a bug I need to have a clear description how to
>> reproduce it together with sample content. If you're able to provide
>> that I'm happy to take a look.
>>
>> BR
>> Maruan
>>
>>> .
>>>
>>> How can I request someone to fix flattening and make it work like in
>>> IText. That is that the right fields are flattened while all the other
>>> fields remain untouched!
>>>
>>>
>>>
>>> ---------------------------------------------------------------------
>>> To unsubscribe, e-mail: [email protected]
>>> For additional commands, e-mail: [email protected]
>>>
>>
>>
>>
>> ---------------------------------------------------------------------
>> To unsubscribe, e-mail: [email protected]
>> For additional commands, e-mail: [email protected]
>>
>
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [email protected]
> For additional commands, e-mail: [email protected]
>
package de.ejb.sf;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.log4j.Logger;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDDocumentCatalog;
import org.apache.pdfbox.pdmodel.PDDocumentInformation;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.PDResources;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceCharacteristicsDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceDictionary;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceStream;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDBorderStyleDictionary;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.pdmodel.interactive.form.PDCheckBox;
import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.apache.pdfbox.pdmodel.interactive.form.PDRadioButton;
import org.apache.pdfbox.pdmodel.interactive.form.PDSignatureField;
import org.apache.pdfbox.pdmodel.interactive.form.PDTextField;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.XMPSchema;
import org.apache.xmpbox.xml.XmpSerializer;

import de.ejb.sf.PDFMetadataInfos;

/**
 * Manipulator
 * 
 * @author F-Sebastian.Felsl
 * @version $Revision:$<br>
 *          $Date:$<br>
 *          $Author:$
 */
public class Manipulator
{
  /** Die CM_VERSION. */
  public static final String CM_VERSION = "$Revision:$ $HeadURL:$";

  @SuppressWarnings("unused")
  private static final Logger LOGGER = Logger.getLogger(Manipulator.class);

  private PDDocument document;

  /**
   * F�gt dem �bergebenen PDF den generierten HashCode aus den AusgabeInfos hinzu
   * 
   * @param mitRisikoFragen mitRisikoFragen
   * @param erstellungAntragAlsZeitpunkt erstellungAntragAlsZeitpunkt
   * @param hashCode hashCode
   * @return Neues PDF mit HashCode
   */
  public byte[] modifyDocument(boolean mitRisikoFragen, Calendar erstellungAntragAlsZeitpunkt, String hashCode)
  {
    try
    {
      final boolean wasEncrypted = document.isEncrypted(); // In some cases the document is not
                                                           // supposed to be encrypted.
      // document.setAllSecurityToBeRemoved(true);
      alterMetaData(erstellungAntragAlsZeitpunkt, hashCode);
      prepareSpecialFields(mitRisikoFragen);
      if (wasEncrypted)
      {
        FieldManipulator.secureDocument(document);
      }
      byte[] pdfData = FieldManipulator.docToByte(document);
      // checkPdf(pdfData);
      return pdfData;
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
    return null;
  }


  private void attachCheckBoxAppearanceStream(PDAnnotationWidget widget)
  {
    PDAppearanceDictionary apD = widget.getAppearance();
    if (apD == null)
    {
      apD = new PDAppearanceDictionary();
      widget.setAppearance(apD);
    }

    try
    {
      PDAppearanceCharacteristicsDictionary acD = widget.getAppearanceCharacteristics();
      if (acD == null)
      {
        acD = new PDAppearanceCharacteristicsDictionary(widget.getCOSObject());
        widget.setAppearanceCharacteristics(acD);
      }
      PDBorderStyleDictionary bD = widget.getBorderStyle();
      if (bD == null)
      {
        bD = new PDBorderStyleDictionary(widget.getCOSObject());
        widget.setBorderStyle(bD);
      }

      acD.setNormalCaption(FieldManipulator.NORMAL_CAPTION);
      acD.setAlternateCaption(FieldManipulator.NORMAL_CAPTION);
      acD.setRolloverCaption(FieldManipulator.NORMAL_CAPTION);

      final float lineWidth = getLineWidth(widget);
      final PDRectangle rect = widget.getRectangle();

      bD.setWidth(lineWidth);
      bD.setStyle(PDBorderStyleDictionary.STYLE_SOLID);
      PDAppearanceStream aS = new PDAppearanceStream(document);
      aS.setBBox(new PDRectangle(rect.getWidth(), rect.getHeight()));
      PDResources res = new PDResources();
      res.add(FieldManipulator.CHECKBOX_FONT);
      aS.setResources(res);
      aS.setFormType(1);
      PDPageContentStream pcAPCS = new PDPageContentStream(document, aS);
      pcAPCS.setLineWidth(lineWidth); // border style (dash) ignored
      pcAPCS.addRect(0, 0, rect.getWidth(), rect.getHeight());
      pcAPCS.beginText();
      pcAPCS.setFont(FieldManipulator.CHECKBOX_FONT, rect.getHeight());
      pcAPCS.setStrokingColor(FieldManipulator.MARK_COLOR);
      pcAPCS.setNonStrokingColor(FieldManipulator.MARK_COLOR);
      pcAPCS.newLineAtOffset(lineWidth * 2, lineWidth * 2);
      pcAPCS.showText(FieldManipulator.checkBoxGlyph);
      pcAPCS.endText();
      pcAPCS.close();

      final COSDictionary normalAppearanceDict =
          (COSDictionary) (apD.getNormalAppearance()).getCOSObject();
      final COSDictionary downAppearanceDict =
          (COSDictionary) (apD.getDownAppearance()).getCOSObject();
      final COSDictionary rollAppearanceDict =
          (COSDictionary) (apD.getRolloverAppearance()).getCOSObject();
      normalAppearanceDict.keySet()
          .stream()
          .forEach(x -> normalAppearanceDict.setItem(x, aS));
      rollAppearanceDict.keySet()
          .stream()
          .forEach(x -> rollAppearanceDict.setItem(x, aS));
      downAppearanceDict.keySet()
          .stream()
          .forEach(x -> downAppearanceDict.setItem(x, aS));
    }
    catch (IOException exception)
    {
    }

  }

  private float getLineWidth(PDAnnotationWidget widget)
  {
    PDBorderStyleDictionary bs = widget.getBorderStyle();
    return (bs != null) ? bs.getWidth() : 1;
  }

  private void changeColor(COSDictionary dict, String style)
  {
    dict.setString(COSName.DA, style);
    dict.setString(COSName.DS, style);
    dict.setNeedToBeUpdated(true);
  }

  private void markCheckBox(PDField field)
  {
    List<PDAnnotationWidget> widgets = field.getWidgets();
    if (widgets == null)
    {
      return;
    }
    LOGGER.debug("Checkbox " + field.getFullyQualifiedName() + " has " + widgets.size() + ".");
    for (PDAnnotationWidget widget : widgets)
    {
      changeColor(widget.getCOSObject(), FieldManipulator.checkboxStyle);
      attachCheckBoxAppearanceStream(widget);
    }
  }

  private void markTextfield(PDField field)
  {
    List<PDAnnotationWidget> widgets = field.getWidgets();
    if (widgets == null)
    {
      return;
    }
    LOGGER.debug("Textfeld " + field.getFullyQualifiedName() + " has " + widgets.size() + ".");
    for (PDAnnotationWidget widget : widgets)
    {
      changeColor(widget.getCOSObject(), FieldManipulator.textfieldStyle);
    }
  }

  private void alterMetaData(Calendar erstellungAntragAlsZeitpunkt, String hashCode)
  {
    final PDDocumentInformation documentInfo = document.getDocumentInformation();
    if(hashCode != null)
    {
      documentInfo.setCustomMetadataValue(PDFMetadataInfos.HASH, hashCode);
    }
    if(erstellungAntragAlsZeitpunkt != null)
    {
      documentInfo.setCreationDate(erstellungAntragAlsZeitpunkt);
    }

    try
    {
      PDDocumentCatalog catalog = document.getDocumentCatalog();
      XMPMetadata xmpMetadata = XMPMetadata.createXMPMetadata();
      xmpMetadata.addSchema(new XMPSchema(xmpMetadata));
      PDMetadata metadata = catalog.getMetadata();
      if (metadata == null)
      {
        metadata = new PDMetadata(document);
      }
      XmpSerializer serializer = new XmpSerializer();
      ByteArrayOutputStream buffer = new ByteArrayOutputStream();
      serializer.serialize(xmpMetadata, buffer, false);
      metadata.importXMPMetadata(buffer.toByteArray());
      catalog.setMetadata(metadata);
    }
    catch (Exception exception)
    {

    }
  }

  private void prepareSpecialFields(boolean mitRisikoFragen)
  {
    try
    {
      if (document == null)
      {
        return;
      }
      final PDDocumentCatalog catalog = document.getDocumentCatalog();
      if (catalog == null)
      {
        return;
      }
      PDAcroForm acroForm = catalog.getAcroForm();
      if (acroForm == null || acroForm.getFields() == null || acroForm.getFields()
          .isEmpty())
      {
        return;
      }

      PDResources res = acroForm.getDefaultResources();
      if (res == null)
      {
        res = new PDResources();
      }
      res.put(FieldManipulator.TEXTFIELD_FONTTYPE, FieldManipulator.TEXTFIELD_FONT);
      res.put(FieldManipulator.CHECKBOX_FONTTYPE, FieldManipulator.CHECKBOX_FONT);
      acroForm.setDefaultResources(res);

      markESignFields(mitRisikoFragen);
      
      final List<PDField> acroFieldsNonEdit = new ArrayList<>();
      final List<PDField> acroFieldsEdit = new ArrayList<>();
      for (int i = acroForm.getFields()
          .size() - 1; i >= 0; i--)
      {
        PDField field = acroForm.getFields()
            .get(i);
        if (!((field instanceof PDTextField) || (field instanceof PDCheckBox)
            || (field instanceof PDRadioButton)))
        {
          LOGGER.debug("Feld " + field.getFullyQualifiedName()
              + " is not relevant and will not be altered.");
          continue;
        }
        if (field.isReadOnly())
        {
          LOGGER.debug(
              "Feld " + field.getFullyQualifiedName() + " is readonly and will be flattened.");
          if(field.getValueAsString().isEmpty())
          {
            acroForm.getFields().remove(field);
          }
          else
          {
            acroFieldsNonEdit.add(0, field);
          }
        }
        else
        {
          acroFieldsEdit.add(0, field);
        }
      }
      // acroForm.flatten(acroFieldsNonEdit, true); // refreshAppearance = false will make the document
      
      for(PDField field: acroFieldsEdit)
      {
        // unusable in eDocBox!
        if (field instanceof PDTextField)
        {
          LOGGER.debug("Textfield " + field.getFullyQualifiedName() + " is not readonly.");
          markTextfield(field);
        }
        else if (field instanceof PDCheckBox)
        {
          LOGGER.debug("Checkbox " + field.getFullyQualifiedName() + " is not readonly.");
          markCheckBox(field);
        }
        else if (field instanceof PDRadioButton)
        {
          LOGGER.debug("Checkbox " + field.getFullyQualifiedName() + " is not readonly.");
          markCheckBox(field);
        }
      }
      // acroForm.flatten();
      acroForm.refreshAppearances(acroFieldsEdit);
      acroForm.setNeedAppearances(true);
    }
    catch (Exception exception)
    {
      exception.printStackTrace();
    }

  }

  /**
   * @param doc doc
   * @return PDDocument
   * @throws Exception Exception
   */
  public PDDocument transformToPdf(byte[] doc) throws Exception
  {
    
    @SuppressWarnings("unused")
    PDDocument reader = Loader.loadPDF(doc, FieldManipulator.PDF_PASSWORD);
    // reader.setVersion(1.8f);// PDF
    // einlesen
    // um es
    // auf
    // Integrität
    // zu
    // prüfen
    return reader;
  }


  /**
   * 
   */
  public void close()
  {
    FieldManipulator.close(document);
  }
  

  /**
   * @param source source
   * @throws SpecialException SpecialException
   */
  public void consumeDocument(byte[] source) throws SpecialException
  {
    try
    {
      document = transformToPdf(source);
    }
    catch (final Exception exception)
    {
      throw new SpecialException(ANTRAG_PDF_NICHT_LESBAR);
    }
  }


  /**
   * Liefert documentFromBase64BinaryString.
   * 
   * @return liefert documentFromBase64BinaryString.
   */
  public PDDocument getPDDocument()
  {
    return document;
  }


}

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to