我正在尝试拆分PDF中的共享XObject流(最初展平具有相同内容的表单字段)。 使用itextsharp执行此操作的正确方法是什么?我正在尝试下面的代码,但流仍然在结果文档中共享。
使用共享XObject流flatten.pdf
的示例pdf PdfReader pdf = new PdfReader(path);
PdfStamper stamper = new PdfStamper(pdf, new FileStream("processed.pdf", FileMode.OpenOrCreate, FileAccess.ReadWrite));
EliminateSharedStreams(stamper, 1);
stamper.Close();
virtual public void EliminateSharedXObjectStreams(PdfStamper pdfStamper, int pageNum)
{
PdfReader pdfReader = pdfStamper.Reader;
PdfDictionary page = pdfReader.GetPageN(pageNum);
PdfDictionary resources = page.GetAsDict(PdfName.RESOURCES);
PdfDictionary xObjResources = resources.GetAsDict(PdfName.XOBJECT);
List<PRIndirectReference> newRefs = new List<PRIndirectReference>();
List<PdfName> newNames = new List<PdfName>();
List<PRStream> newStreams = new List<PRStream>();
IntHashtable visited = new IntHashtable();
foreach (PdfName key in xObjResources.Keys)
{
PdfStream xObj = xObjResources.GetAsStream(key);
if (xObj is PRStream && xObj.GetAsName(PdfName.SUBTYPE) != null &&
xObj.GetAsName(PdfName.SUBTYPE).CompareTo(PdfName.FORM) == 0)
{
PRIndirectReference refi = (PRIndirectReference)xObjResources.Get(key);
PRStream xFormStream = (PRStream)xObj;
if (visited.ContainsKey(refi.Number))
{
// need to duplicate
newRefs.Add(refi);
PRStream newStream = new PRStream(xFormStream, null);
newStreams.Add(newStream);
newNames.Add(key);
}
else
visited[xFormStream.ObjNum] = 1;
}
}
if (newStreams.Count == 0)
return;
PdfContentByte canvas = pdfStamper.GetOverContent(pageNum);
PdfWriter writer = pdfStamper.Writer;
for (int k = 0; k < newStreams.Count; ++k)
{
canvas.SaveState();
//add copied stream
PdfIndirectReference newRef = writer.AddToBody(newStreams[k]).IndirectReference;
//change the ref
xObjResources.Put(newNames[k], newRef);
canvas.RestoreState();
}
}
答案 0 :(得分:0)
您的代码可能无法按预期运行的原因有很多。由于您没有提供PDF样本,我无法分辨哪些更相关,哪些不相关。
您只搜索在同一页面上共享的xobjects;如果xobject曾经在第一页和第二页上使用过一次,那么你的代码就无法识别它。
如果您希望能够找到此类共享,则至少必须在IntHashtable
的所有visited
来电中使用相同的EliminateSharedXObjectStreams
实例PdfStamper pdfStamper
{{1}例如通过在此方法之外创建一次并使其成为方法的参数。
您只检查直接页面资源中的共享xobjects。但是表单xobjects有自己的资源,可以包含更多形式的xobject声明。
如果你想找到这样的份额,你将不得不进入页面的xobjects,xobjects的xobjects等的资源。
(严格来说,你还必须进入模式的xobjects和Type 3 Font字形定义的形式,但这些不太可能将表单字段压平。)
您只检查具有不同名称的共享xobject。但是,也可以通过从同一内容流多次引用相同的名称来共享xobjects。
如果要查找此类共享,则必须分析相关内容流,以查找具有相同名称的xobject表单的重复用法。
(顺便说一句,这样做你也可以检查是否使用了声明的xobjects:如果在某些资源中声明了一个表单xobject,这并不意味着它在这些资源的上下文中使用,它可能是一个未使用的资源。)
您没有标记xObjResources
(如果它本身是间接的)或page
(否则)。如果您的PdfStamper pdfStamper
在附加模式下工作,则您的更改可能会被忽略。
提供
信息后事实证明,上述问题与您的情况无关。同时你也提供了一个示例文档,我可以重现这个问题。它是单页文档,包含直接页面资源中的共享流(具有不同名称的xobject)。 pdfStamper不是追加模式。
实际上,您的代码不会拆分共享的XObject。原因是PdfStamper
用于在PdfReader
中处理压模构造时的状态中的PDF,仅使用压模方法。另一方面,您的代码在构造压模之后操纵直接从PdfReader
检索的对象。因此,当您的新流添加到PDF(实际上是前面的)时,预先存在的XObject资源字典中的更改不会使其成为结果。
如果您想操纵从阅读器中检索的对象,则应在创建压模之前执行此操作。
这实际上应该适合您,因为您的代码在结构上从Pdfreader
方法复制,EliminateSharedStreams
,您根据自己的用例进行了调整。
唯一的问题是该方法使用PdfReader
类的隐藏成员变量。但是你可以通过反射来访问那个变量。
因此,操纵方法(处理纯PdfReader
)可能如下所示:
virtual public void EliminateSharedXObjectStreams(PdfReader pdfReader, int pageNum)
{
PdfDictionary page = pdfReader.GetPageN(pageNum);
PdfDictionary resources = page.GetAsDict(PdfName.RESOURCES);
PdfDictionary xObjResources = resources.GetAsDict(PdfName.XOBJECT);
List<PRIndirectReference> newRefs = new List<PRIndirectReference>();
List<PRStream> newStreams = new List<PRStream>();
IntHashtable visited = new IntHashtable();
foreach (PdfName key in xObjResources.Keys)
{
PdfStream xObj = xObjResources.GetAsStream(key);
if (xObj is PRStream && xObj.GetAsName(PdfName.SUBTYPE) != null &&
xObj.GetAsName(PdfName.SUBTYPE).CompareTo(PdfName.FORM) == 0)
{
PRIndirectReference refi = (PRIndirectReference)xObjResources.Get(key);
PRStream xFormStream = (PRStream)xObj;
if (visited.ContainsKey(refi.Number))
{
// need to duplicate
newRefs.Add(refi);
PRStream newStream = new PRStream(xFormStream, null);
newStreams.Add(newStream);
}
else
visited[xFormStream.ObjNum] = 1;
}
}
if (newStreams.Count == 0)
return;
FieldInfo xrefObjField = typeof(PdfReader).GetField("xrefObj", BindingFlags.Instance | BindingFlags.NonPublic);
List<PdfObject> xrefObj = (List<PdfObject>)xrefObjField.GetValue(pdfReader);
for (int k = 0; k < newStreams.Count; ++k)
{
xrefObj.Add(newStreams[k]);
PRIndirectReference refi = newRefs[k];
refi.SetNumber(xrefObj.Count - 1, 0);
}
}
你可以像这样使用它:
using (PdfReader pdfReader = new PdfReader(sourcePath))
using (Stream pdfStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write))
{
EliminateSharedXObjectStreams(pdfReader, 1);
PdfStamper pdfStamper = new PdfStamper(pdfReader, pdfStream);
pdfStamper.Close();
}
特别是在构建EliminateSharedXObjectStreams
之前调用PdfStamper
。
如果您正在使用通用解决方案,那么您当然必须扩展该方法以消除在答案的第一部分中观察到的限制......
操作PdfReader按预期工作。唯一的问题是,不是使用
xrefObj
私有字段,而是可以使用AddPdfObject
添加流:for (int k = 0; k < newStreams.Count; ++k) { PRIndirectReference newRef = pdfReader.AddPdfObject(newStreams[k]); PRIndirectReference refi = newRefs[k]; refi.SetNumber(newRef.Number, 0); }
实际上,这大大改善了解决方案。