【问题标题】:Get font and size of an acroform field获取 acroform 字段的字体和大小
【发布时间】:2015-01-23 12:28:18
【问题描述】:

我需要访问 acroform 文本字段的字体样式和大小。我可以通过pdfclown 访问Field 对象,但不知道如何访问字体。有人知道怎么做吗?

【问题讨论】:

  • Java 或 C# 版本的 PdfClown?默认外观字体和大小还是实际外观?
  • Java。我想要控件的实际外观。原因是我使用带有 acroforms 的模板并从 java 代码填充字段。我将从最终生成的文件中删除控件并将其替换为相同外观的文本,因为 iPhone 和 Android 上的默认 PDF 阅读器根本不显示 acroforms。基本上我想将 PDF 完全展平,从生成的文件中删除所有 acroform 控件,但保留它们的值。
  • 好的,在这种情况下,您应该简单地使用该字段的正常外观,将其转换为 XObject,然后从内容流中引用该 XObject。你根本不需要知道细节。我对 PdfClown 不太精通,所以我将不得不查找如何做到这一点。这个周末可能没有时间。
  • 好的,原来的 PDF Clown 开发者速度更快... ;)

标签: pdfclown


【解决方案1】:

我很高兴地向您宣布,今天我致力于在当前分支 (@987654322 @, rev 129, 131, 133) 和 trunk (rev 130, 132, 134):我建议您直接从该 SVN 存储库下载以获得最新的功能和修复.

否则,如果您坚持使用已发布的版本 (0.1.2.0),这里是相关代码(再次,我推荐提交的版本,因为它更精致)。首先,我们需要进行一些调整:

1) 在XObject.java 中替换wrap 方法:

public static XObject wrap(
  PdfDirectObject baseObject
  )
{
  if(baseObject == null)
    return null;

  PdfName subtype = (PdfName)((PdfStream)baseObject.resolve()).getHeader().get(PdfName.Subtype);
  if(PdfName.Form.equals(subtype))
    return FormXObject.wrap(baseObject);
  else if(PdfName.Image.equals(subtype))
    return ImageXObject.wrap(baseObject);
  else
    return null;
}

2) 在FormXObject.java 中替换wrap 方法:

public static FormXObject wrap(
  PdfDirectObject baseObject
  )
{
  if(baseObject == null)
    return null;

  PdfDictionary header = ((PdfStream)PdfObject.resolve(baseObject)).getHeader();
  PdfName subtype = (PdfName)header.get(PdfName.Subtype);
  /*
    NOTE: Sometimes the form stream's header misses the mandatory Subtype entry; therefore, here
    we force integrity for convenience (otherwise, content resource allocation may fail, for 
    example in case of Acroform flattening).
  */
  if(subtype == null && header.containsKey(PdfName.BBox))
  {header.put(PdfName.Subtype, PdfName.Form);}
  else if(!subtype.equals(PdfName.Form))
    return null;

  return new FormXObject(baseObject);
}

那么我们就可以定义实际的form flattener了:

3) 将 FormFlatener.java 添加到 org.pdfclown.tools 包中:

package org.pdfclown.tools;

import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

import org.pdfclown.documents.Document;
import org.pdfclown.documents.Page;
import org.pdfclown.documents.PageAnnotations;
import org.pdfclown.documents.contents.xObjects.FormXObject;
import org.pdfclown.documents.interaction.annotations.Annotation.FlagsEnum;
import org.pdfclown.documents.interaction.annotations.Widget;
import org.pdfclown.documents.interaction.forms.Field;
import org.pdfclown.documents.interaction.forms.Fields;
import org.pdfclown.documents.interaction.forms.Form;
import org.pdfclown.objects.PdfArray;
import org.pdfclown.objects.PdfDictionary;
import org.pdfclown.objects.PdfDirectObject;
import org.pdfclown.objects.PdfName;
import org.pdfclown.objects.PdfObjectWrapper;
import org.pdfclown.objects.PdfReference;
import org.pdfclown.tools.PageStamper;
import org.pdfclown.util.math.geom.Dimension;

public class FormFlattener
{
  private boolean hiddenRendered;
  private boolean nonPrintableRendered;

  /**
    Replaces the Acroform fields with their corresponding graphics representation.

    @param document Document to flatten.
  */
  public void flatten(
    Document document
    )
  {
    Map<PdfDirectObject,PageStamper> pageStampers = new HashMap<PdfDirectObject,PageStamper>();
    Form form = document.getForm();
    Fields formFields = form.getFields();
    for(Field field : formFields.values())
    {
      for(Widget widget : field.getWidgets())
      {
        Page widgetPage = widget.getPage();
        EnumSet<FlagsEnum> flags = widget.getFlags();
        // Is the widget to be rendered?
        if((!flags.contains(FlagsEnum.Hidden) || hiddenRendered)
          && (flags.contains(FlagsEnum.Print) || nonPrintableRendered))
        {
          // Stamping the current state appearance of the widget...
          PdfName widgetCurrentState = (PdfName)widget.getBaseDataObject().get(PdfName.AS);
          FormXObject widgetCurrentAppearance = widget.getAppearance().getNormal().get(widgetCurrentState);
          if(widgetCurrentAppearance != null)
          {
            PageStamper widgetStamper = pageStampers.get(widgetPage.getBaseObject());
            if(widgetStamper == null)
            {pageStampers.put(widgetPage.getBaseObject(), widgetStamper = new PageStamper(widgetPage));}

            Rectangle2D widgetBox = widget.getBox();
            widgetStamper.getForeground().showXObject(widgetCurrentAppearance, new Point2D.Double(widgetBox.getX(), widgetBox.getY()), new Dimension(widgetBox.getWidth(), widgetBox.getHeight()));
          }
        }

        // Removing the widget from the page annotations...
        PageAnnotations widgetPageAnnotations = widgetPage.getAnnotations();
        widgetPageAnnotations.remove(widget);
        if(widgetPageAnnotations.isEmpty())
        {
          widgetPage.getBaseDataObject().put(PdfName.Annots, null);
          widgetPageAnnotations.delete();
        }

        // Removing the field references relating the widget...
        PdfDictionary fieldPartDictionary = widget.getBaseDataObject();
        while (fieldPartDictionary != null)
        {
          PdfDictionary parentFieldPartDictionary = (PdfDictionary)fieldPartDictionary.resolve(PdfName.Parent);

          PdfArray kidsArray;
          if(parentFieldPartDictionary != null)
          {kidsArray = (PdfArray)parentFieldPartDictionary.resolve(PdfName.Kids);}
          else
          {kidsArray = formFields.getBaseDataObject();}

          kidsArray.remove(fieldPartDictionary.getReference());
          fieldPartDictionary.getReference().delete();

          if(!kidsArray.isEmpty())
            break;

          fieldPartDictionary = parentFieldPartDictionary;
        }
      }
    }
    if(formFields.isEmpty())
    {
      // Removing the Acroform root...
      document.setForm(null);
      form.delete();
    }
    for(PageStamper pageStamper : pageStampers.values())
    {pageStamper.flush();}
  }

  /**
    Gets whether hidden fields have to be rendered.
  */
  public boolean isHiddenRendered(
    )
  {return hiddenRendered;}

  /**
    Gets whether non-printable fields have to be rendered.
  */
  public boolean isNonPrintableRendered(
    )
  {return nonPrintableRendered;}

  /**
    @see #isHiddenRendered()
  */
  public FormFlattener setHiddenRendered(
    boolean value
    )
  {
    hiddenRendered = value;
    return this;
  }

  /**
    @see #isNonPrintableRendered()
  */
  public FormFlattener setNonPrintableRendered(
    boolean value
    )
  {
    nonPrintableRendered = value;
    return this;
  }
}

这是一个使用它的例子:

import java.io.IOException;

import org.pdfclown.documents.Document;
import org.pdfclown.files.File;
import org.pdfclown.tools.FormFlattener;

File file = null;
try
{
  // 1. Opening the PDF file...
  {
    try
    {file = new File(myFilePath);}
    catch(Exception e)
    {throw new RuntimeException(myFilePath + " file access error.",e);}
  }
  Document document = file.getDocument();

  // 2. Flatten the form!
  FormFlattener formFlattener = new FormFlattener();
  formFlattener.flatten(document);

  // 3. Serialize the PDF file!
  try
  {file.save(SerializationModeEnum.Standard);}
  catch(Exception e)
  {
    System.out.println("File writing failed: " + e.getMessage());
    e.printStackTrace();
  }
}
finally
{
  // 4. Closing the PDF file...
  if(file != null)
  {
    try
    {file.close();}
    catch(IOException e)
    {/* NOOP */}
  }
}

为了及时了解该项目的最新动态,您可以在其网站 (pdfclown.org) 和 Twitter 流 (https://twitter.com/pdfclown) 上关注 PDF Clown。

【讨论】:

  • 看起来不错。不过要注意两点: A 您只为带有!flags.contains(FlagsEnum.Hidden) &amp;&amp; flags.contains(FlagsEnum.Print) 的小部件添加外观;我将允许调用者选择是否应添加隐藏或非打印字段的外观。 B 看起来您无法将没有外观的字段可视化。特别是如果 PDF 有 /NeedAppearances true,代码应该尝试在需要的地方创建外观。
  • 我同意你的两个考虑,谢谢!我将添加一个选项来解锁隐藏和不可打印的字段(用户将遵守避免在生产环境中使用它的格式策略)。外观合成对于高质量渲染很重要,但为了测试真实案例,我更愿意在没有外观的领域的实际样本上工作:请不要犹豫,向我报告包含此类领域的文件,谢谢!
  • 我现在手头没有。如果我碰巧遇到一个,我会指给你看。
  • @StefanoChizzolini 非常感谢。可以确认我现在可以在 iPhone 和 Android 上看到表单值了。
【解决方案2】:
public void flatten(Document document) {
    Map<Page, PageStamper> pageStampers = new HashMap<Page, PageStamper>();
    Form form = document.getForm();
    Fields formFields = form.getFields();
    for (Field field : formFields.values()) {
        for (Widget widget : field.getWidgets()) {
            Page widgetPage = widget.getPage();             
            PageStamper widgetStamper = pageStampers.get(widgetPage);
            if (widgetStamper == null) {
                pageStampers.put(widgetPage,widgetStamper = new PageStamper(widgetPage));
            }
            PrimitiveComposer composer = widgetStamper.getForeground();
            composer.setFont(new StandardType1Font(document,StandardType1Font.FamilyEnum.Courier, false, false), 7);                                    
            composer.showText(field.getName(), new Point2D.Double(widget.getBox().getX()+3, widget.getBox().getY()+3));

        }
        field.delete();
    }
    if (formFields.isEmpty()) {
        // Removing the form root...
        document.setForm(null);
        form.delete();
    }
    for (PageStamper pageStamper : pageStampers.values()) {
        pageStamper.flush();
    }
}

【讨论】:

  • 现在字段已从文档中完全删除
  • 请为您的答案添加一些解释。例如。与@Stefano 相比,这是一种改进。
  • 霍华德,请让我知道您回答的目标(因为它发布的时间比我的晚得多,我希望您解决了我的解决方案中的一些缺陷,我将不胜感激任何改进建议)。谢谢!
猜你喜欢
  • 2012-06-08
  • 1970-01-01
  • 1970-01-01
  • 2011-06-19
  • 1970-01-01
  • 1970-01-01
  • 2015-06-19
  • 2011-10-21
  • 2016-12-08
相关资源
最近更新 更多