从 PDF 中获取所有 SDF/COS 个对象
Get all SDF/COS objects from PDF
我正在尝试使用 PDFNet 7.0.4
和 netcoreapp3.1
获取 PDF 文档中所有 SDF/COS 对象的列表。使用不同的 PDF 解析器,我知道该文档中总共有 570 个 COS 对象,包括 3 张图像。
最初我使用 PDFDoc
加载文档,并遍历页面只是为了寻找 Element
类型 e_image
或 e_inline_image
的对象,但这只会产生3 张图片中的 2 张。在更大的文件中,它的表现更糟;约 2600 张图片中的 0 张。
现在,我退后一步并尝试通过 SDFDoc
进行较低级别的搜索。我可以获得一个预告片对象,然后遍历它,递归任何 e_dict
或 e_stream
对象,并返回任何看起来像真实对象的东西(即任何实际上具有对象编号和世代的东西) .
IEnumerable<Obj> Recurse(Obj root)
{
var idHash = new HashSet<PdfIdentifier>();
return Recurse(root, idHash);
static IEnumerable<Obj> Recurse(Obj obj, HashSet<PdfIdentifier> idHash)
{
var id = obj.ToPdfIdentifier();
if (!idHash.Contains(id))
{
if (id != nullIdentifier)
{
idHash.Add(id);
yield return obj;
}
if (obj.GetType().OneOf(Obj.ObjType.e_dict, Obj.ObjType.e_stream))
{
for (var iter = obj.GetDictIterator(); iter.HasNext(); iter.Next())
{
foreach (var child in Recurse(iter.Value(), idHash))
{
yield return child;
}
}
}
}
}
}
static PdfIdentifier nullIdentifier = new PdfIdentifier() { Generation = 0, ObjectNum = 0 };
ToPdfIdentifier
是获取对象编号和生成的简单扩展方法:
public static PdfIdentifier ToPdfIdentifier(this pdftron.SDF.Obj obj) => new PdfIdentifier { ObjectNum = obj.GetObjNum(), Generation = obj.GetGenNum() };
这运行正常,但只有 returns 45 个对象,其中 none 个是我真正感兴趣的图像。
如何简单地从文档中获取所有 COS 对象?
编辑
这是我们试图获取所有图像的原始 PDFDoc
代码:
private IEnumerable<(PdfIdentifier id, Element el)> GetImages(Stream stream)
{
var doc = new PDFDoc(stream);
var reader = new ElementReader();
for (var iter = doc.GetPageIterator(); iter.HasNext(); iter.Next())
{
reader.Begin(iter.Current());
var el = reader.Next();
while (el != null)
{
var type = el.GetType();
if (el.GetType().OneOf(Element.Type.e_image, Element.Type.e_inline_image))
{
var obj = el.GetXObject();
var id = el.GetXObject().ToPdfIdentifier();
yield return (id, el);
}
el = reader.Next();
}
reader.End();
}
}
这种方法之所以有效,是因为它返回了一些图像,但不是全部。对于一些示例文档,它返回全部,对于一些它返回一个子集,对于一些它返回 none。
编辑
仅供将来参考,多亏了 Ryan 的以下回答,我们最终得到了一对漂亮干净的扩展方法:
public static IEnumerable<SDF.Obj> GetAllObj(this SDF.SDFDoc sdfDoc)
{
var xrefTableSize = sdfDoc.XRefSize();
for (int objNum = 0; objNum < xrefTableSize; objNum++)
{
var obj = sdfDoc.GetObj(objNum);
if (obj.IsFree())
{
continue;
}
else
{
yield return obj;
}
}
}
和
public static string Subtype(this SDF.Obj obj) => obj.FindObj("Subtype") switch
{
null => null,
var s when s.IsName() => s.GetName(),
var s when s.IsString() => s.GetAsPDFText(),
_ => throw new Exception("COS object has an invalid Subtype entry")
};
现在我们可以像 sdfDoc.GetAllObj().Where(o => o.IsStream() && o.Subtype() == "Image");
甚至使用 Linq 一样简单地获取图像:
from o in sdfDoc.GetAllObj()
where o.IsStream() && o.Subtype() == "Image"
select new Image(o);
如果你想获取PDF页面上实际使用的图像(以防PDF中恰好有未使用的图像),那么你可以使用这个示例代码。此代码将具有包含内联图像的额外好处。
https://www.pdftron.com/documentation/samples/dotnetcore/cs/ImageExtractTest
虽然上面的速度可能很慢,但如果文档有数百或数千页,图形化会很复杂。
如您所述,另一种方法是迭代 COS 对象。以下 C# 代码查找所有图像流。请注意,PDF 标准明确规定流必须是间接对象。所以我认为您可以安全地省略读取所有直接对象。
using (PDFDoc doc = new PDFDoc("2002.04610.pdf"))
{
doc.InitSecurityHandler();
int xrefSz = doc.GetSDFDoc().XRefSize();
for (int xrefCounter = 0; xrefCounter < xrefSz; ++xrefCounter)
{
Obj o = doc.GetSDFDoc().GetObj(xrefCounter);
if (o.IsFree())
{
continue;
}
if(o.IsStream())
{
Obj subtypeObj = o.FindObj("Subtype");
if (subtypeObj != null)
{
string subtype = "";
if(subtypeObj.IsName()) subtype = subtypeObj.GetName();
if(subtypeObj.IsString()) subtype = subtypeObj.GetAsPDFText(); // Subtype should be a Name, but just in case
if (subtype.CompareTo("Image") == 0)
{
Console.WriteLine("Indirect object {0} is an Image Stream", o.GetObjNum());
}
}
}
}
}
我正在尝试使用 PDFNet 7.0.4
和 netcoreapp3.1
获取 PDF 文档中所有 SDF/COS 对象的列表。使用不同的 PDF 解析器,我知道该文档中总共有 570 个 COS 对象,包括 3 张图像。
最初我使用 PDFDoc
加载文档,并遍历页面只是为了寻找 Element
类型 e_image
或 e_inline_image
的对象,但这只会产生3 张图片中的 2 张。在更大的文件中,它的表现更糟;约 2600 张图片中的 0 张。
现在,我退后一步并尝试通过 SDFDoc
进行较低级别的搜索。我可以获得一个预告片对象,然后遍历它,递归任何 e_dict
或 e_stream
对象,并返回任何看起来像真实对象的东西(即任何实际上具有对象编号和世代的东西) .
IEnumerable<Obj> Recurse(Obj root)
{
var idHash = new HashSet<PdfIdentifier>();
return Recurse(root, idHash);
static IEnumerable<Obj> Recurse(Obj obj, HashSet<PdfIdentifier> idHash)
{
var id = obj.ToPdfIdentifier();
if (!idHash.Contains(id))
{
if (id != nullIdentifier)
{
idHash.Add(id);
yield return obj;
}
if (obj.GetType().OneOf(Obj.ObjType.e_dict, Obj.ObjType.e_stream))
{
for (var iter = obj.GetDictIterator(); iter.HasNext(); iter.Next())
{
foreach (var child in Recurse(iter.Value(), idHash))
{
yield return child;
}
}
}
}
}
}
static PdfIdentifier nullIdentifier = new PdfIdentifier() { Generation = 0, ObjectNum = 0 };
ToPdfIdentifier
是获取对象编号和生成的简单扩展方法:
public static PdfIdentifier ToPdfIdentifier(this pdftron.SDF.Obj obj) => new PdfIdentifier { ObjectNum = obj.GetObjNum(), Generation = obj.GetGenNum() };
这运行正常,但只有 returns 45 个对象,其中 none 个是我真正感兴趣的图像。
如何简单地从文档中获取所有 COS 对象?
编辑
这是我们试图获取所有图像的原始 PDFDoc
代码:
private IEnumerable<(PdfIdentifier id, Element el)> GetImages(Stream stream)
{
var doc = new PDFDoc(stream);
var reader = new ElementReader();
for (var iter = doc.GetPageIterator(); iter.HasNext(); iter.Next())
{
reader.Begin(iter.Current());
var el = reader.Next();
while (el != null)
{
var type = el.GetType();
if (el.GetType().OneOf(Element.Type.e_image, Element.Type.e_inline_image))
{
var obj = el.GetXObject();
var id = el.GetXObject().ToPdfIdentifier();
yield return (id, el);
}
el = reader.Next();
}
reader.End();
}
}
这种方法之所以有效,是因为它返回了一些图像,但不是全部。对于一些示例文档,它返回全部,对于一些它返回一个子集,对于一些它返回 none。
编辑
仅供将来参考,多亏了 Ryan 的以下回答,我们最终得到了一对漂亮干净的扩展方法:
public static IEnumerable<SDF.Obj> GetAllObj(this SDF.SDFDoc sdfDoc)
{
var xrefTableSize = sdfDoc.XRefSize();
for (int objNum = 0; objNum < xrefTableSize; objNum++)
{
var obj = sdfDoc.GetObj(objNum);
if (obj.IsFree())
{
continue;
}
else
{
yield return obj;
}
}
}
和
public static string Subtype(this SDF.Obj obj) => obj.FindObj("Subtype") switch
{
null => null,
var s when s.IsName() => s.GetName(),
var s when s.IsString() => s.GetAsPDFText(),
_ => throw new Exception("COS object has an invalid Subtype entry")
};
现在我们可以像 sdfDoc.GetAllObj().Where(o => o.IsStream() && o.Subtype() == "Image");
甚至使用 Linq 一样简单地获取图像:
from o in sdfDoc.GetAllObj()
where o.IsStream() && o.Subtype() == "Image"
select new Image(o);
如果你想获取PDF页面上实际使用的图像(以防PDF中恰好有未使用的图像),那么你可以使用这个示例代码。此代码将具有包含内联图像的额外好处。 https://www.pdftron.com/documentation/samples/dotnetcore/cs/ImageExtractTest
虽然上面的速度可能很慢,但如果文档有数百或数千页,图形化会很复杂。
如您所述,另一种方法是迭代 COS 对象。以下 C# 代码查找所有图像流。请注意,PDF 标准明确规定流必须是间接对象。所以我认为您可以安全地省略读取所有直接对象。
using (PDFDoc doc = new PDFDoc("2002.04610.pdf"))
{
doc.InitSecurityHandler();
int xrefSz = doc.GetSDFDoc().XRefSize();
for (int xrefCounter = 0; xrefCounter < xrefSz; ++xrefCounter)
{
Obj o = doc.GetSDFDoc().GetObj(xrefCounter);
if (o.IsFree())
{
continue;
}
if(o.IsStream())
{
Obj subtypeObj = o.FindObj("Subtype");
if (subtypeObj != null)
{
string subtype = "";
if(subtypeObj.IsName()) subtype = subtypeObj.GetName();
if(subtypeObj.IsString()) subtype = subtypeObj.GetAsPDFText(); // Subtype should be a Name, but just in case
if (subtype.CompareTo("Image") == 0)
{
Console.WriteLine("Indirect object {0} is an Image Stream", o.GetObjNum());
}
}
}
}
}