从 PDF 中获取所有 SDF/COS 个对象

Get all SDF/COS objects from PDF

我正在尝试使用 PDFNet 7.0.4netcoreapp3.1 获取 PDF 文档中所有 SDF/COS 对象的列表。使用不同的 PDF 解析器,我知道该文档中总共有 570 个 COS 对象,包括 3 张图像。

最初我使用 PDFDoc 加载文档,并遍历页面只是为了寻找 Element 类型 e_imagee_inline_image 的对象,但这只会产生3 张图片中的 2 张。在更大的文件中,它的表现更糟;约 2600 张图片中的 0 张。

现在,我退后一步并尝试通过 SDFDoc 进行较低级别的搜索。我可以获得一个预告片对象,然后遍历它,递归任何 e_dicte_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());
                }
            }
        }
    }
}