获取多行 AcroField 的行数

Get row count of a multiline AcroField

我有一个带有 AcroFields 的 PDF。一些字段是多行字段。我需要用给定的文本填充 AcroFields,并用“*”(或预定义的 character/string)填充字段中剩余的任何 space。我可以使用 iText 添加文本,但不知道如何计算要添加的正确填充量。

请你建议我如何做到这一点。谢谢。

我写的代码是:

 public string CreatePdf(IDictionary<FieldKey, string> dictionary, string template, string saveAs)
 {
      // Create new PDF from template
      using (PdfReader reader = new PdfReader(template))
      using (FileStream stream = new FileStream(saveAs, FileMode.Create, FileAccess.ReadWrite))
      using (PdfStamper stamper = new PdfStamper(reader, stream))
      {
          // Populate PDF fields from dictionary
          AcroFields formFields = stamper.AcroFields;
          foreach (KeyValuePair<FieldKey, string> dataField in dictionary)
          {
              switch (dataField.Key.FieldType)
              {
                  case FieldType.Text:              
                  string fieldValue = dataField.Value;
                  // Add filler if a filler is set
                  if (!String.IsNullOrWhiteSpace(dataField.Key.FillRight))
                  {
                      fieldValue = GetTextWithFiller(formFields, dataField.Key.FieldName, fieldValue, dataField.Key.FillRight);
                  }
                  // Text field
                  if (!formFields.SetField(dataField.Key.FieldName, fieldValue))
                      throw new InvalidDataException(String.Format("Invalid Template Field: {0} in Template: {1}", dataField.Key.FieldName, template));
                  break;
                  case FieldType.Image:
                      // Image field
                      PlaceImage(formFields, dataField);
                      break;
                  case FieldType.Barcode2Of5:
                      // 2 of 5 Barcode
                      PlaceBarcode2Of5(dataField.Value, stamper);
                      break;
                  case FieldType.Barcode128:
                      // 2 of 5 Barcode
                      PlaceBarcode128(dataField.Value, stamper);
                      break;
                  default:
                      throw new InvalidDataException(String.Format("Invalid data filed type : {0}", dataField.Key.FieldType));
              }
          }
          // Save PDF
          reader.RemoveUnusedObjects();
          stamper.FormFlattening = true;
          stamper.Close();
      }
      return saveAs;
  }

以及获取填充物的方法,它没有像我预期的那样工作:

private static string GetTextWithFiller(AcroFields fields, string fieldName, string text, string filler)
{        
    // Get the size of the rectangle that defines the field
    AcroFields.FieldPosition fieldPosition = fields.GetFieldPositions(fieldName)[0];
    Rectangle rect = fieldPosition.position;
    // Get field font
    PdfDictionary merged = fields.GetFieldItem(fieldName).GetMerged(0);
    TextField textField = new TextField(null, null, null);
    fields.DecodeGenericDictionary(merged, textField);
    Font fieldFont = new Font(textField.Font);
    Chunk whatWeHave = new Chunk(text, fieldFont);
    float textWidth = whatWeHave.GetWidthPoint();
    // See how far the text field is filled with give text
    float textEndPoint = rect.Left + textWidth;
    float rectBottom = rect.Bottom;
    float rectRight = rect.Right;
    float rectTop = rect.Top;
    // How many rows to fill
    int textRows = Convert.ToInt32(rect.Height / fieldFont.CalculatedSize);
    float totalCharactersWeCanFit = rect.Width * textRows;
    if (textWidth < totalCharactersWeCanFit)
        {
        // Get the width of filler character
        Chunk fillCharWidth = new Chunk(filler, fieldFont);
        // Available gap
        float gap = totalCharactersWeCanFit - textWidth;
        // How much filler required
        int fillAmount = Convert.ToInt32(gap / fillCharWidth.GetWidthPoint());
        // Fill with filler
        StringBuilder tempString = new StringBuilder();
        tempString.Append(text);
        for (int n = 0; n < fillAmount; ++n)
        {
            tempString.Append(filler);
        }
        text = tempString.ToString();
    }
    return text;
}

考虑一个包含三个多行文本字段的表单:

对于第一个字段,我们定义了一个字体大小0(这也是你做的);对于第二个字段,我们定义了一个字体 12;对于第三个字段,我们定义了一个字体 6.

现在让我们填写并拼合表格:

public void manipulatePdf(String src, String dest) throws DocumentException, IOException {
    PdfReader reader = new PdfReader(src);
    PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest));
    AcroFields form = stamper.getAcroFields();
    StringBuilder sb = new StringBuilder();
    for (String name : form.getFields().keySet()) {
        int n = getInformation(form, name);
        for (int i = 0; i < n; i++) {
            sb.append(" *");
        }
        String filler = sb.toString();
        form.setField(name, name + filler);
    }
    stamper.setFormFlattening(true);
    stamper.close();
    reader.close();
}

结果如下所示:

如您所见,我们作为填充符添加的 * 的数量取决于在字段级别定义的字体大小。如果字体大小为 0,则字体将以文本始终适合的方式进行调整。如果字体有实际值,我们可以或多或少地计算行数,"columns" 我们需要:

public int getInformation(AcroFields form, String name) {
    form.getFieldItem(name);
    AcroFields.Item item = form.getFieldItem(name);
    PdfDictionary dict = item.getMerged(0);
    PdfString da = dict.getAsString(PdfName.DA);
    Object[] da_values = AcroFields.splitDAelements(da.toUnicodeString());
    if (da_values == null) {
        System.out.println("No default appearance");
    }
    BaseFont bf = null;
    String font = (String)da_values[AcroFields.DA_FONT];
    if (font != null) {
        PdfDictionary dr = dict.getAsDict(PdfName.DR);
        if (dr != null) {
            PdfDictionary fontDict = dr.getAsDict(PdfName.FONT);
            bf = BaseFont.createFont((PRIndirectReference)fontDict.get(new PdfName(font)));
        }
    }
    if (bf == null) {
        System.out.println("No BaseFont");
    }
    else {
        System.out.println("Basefont: " + bf.getPostscriptFontName());
        System.out.println("Size: " + da_values[AcroFields.DA_SIZE]);
        Float size = (Float)da_values[AcroFields.DA_SIZE];
        if (size == 0)
            return 1000;
        Rectangle rect = form.getFieldPositions(name).get(0).position;
        float factor = bf.getFontDescriptor(BaseFont.BBOXURY, 1) - bf.getFontDescriptor(BaseFont.BBOXLLY, 1);
        int rows = Math.round(rect.getHeight() / (size * factor) + 0.5f);
        int columns = Math.round(rect.getWidth() / bf.getWidthPoint(" *", size) + 0.5f);
        System.out.println("height: " + rect.getHeight() + "; width: " + rect.getWidth());
        System.out.println("rows: " + rows + "; columns: " + columns);
        return rows * columns;
    }
    return 1000;
}

首先我们获取字体,以便我们可以创建一个 BaseFont 对象。然后我们得到字体大小并使用存储在 BaseFont 中的信息,我们将定义用于计算行距的因子(即两行之间的 space)。

我们还询问了字段的尺寸。然后我们计算可以将多少行放入字段矩形的高度 (rows) 中,以及将 String " *" 放入字段矩形的宽度 (columns) 中。如果我们将 columnsrows 相乘,我们将得到一个近似值,即我们必须添加 " *" 多少次才能获得适当的填充符。如果我们有太多填充物也没关系:如果定义了字体,所有不适合的文本都将被删除。

您可以在此处找到完整示例:MultiLineFieldCount

我们采用 multiline.pdf 形式和 getInformation() 方法 returns 此信息:

Basefont: Helvetica
Size: 0.0
Basefont: Helvetica
Size: 6.0
height: 86.0; width: 108.0
rows: 13; columns: 27
Basefont: Helvetica
Size: 12.0
height: 86.0; width: 107.999985
rows: 7; columns: 14

关于第一个字段,我们不能说太多,因为字体大小为 0。在您的示例中也是如此。如果字体为 0,则您的问题无法回答。您无法计算出有多少 * 适合该字段,因为您不知道将用于呈现 *.

的字体大小

如果字体大小为12,我们可以容纳7行14列(我们在屏幕截图中看到7行13列)。如果字体大小为6,我们可以放14行27列(我们在截图中看到的是14行26列)。

多出一列是因为我们使用了ceil()方法。高估列数总比低估列数好...