首页 > 解决方案 > 从 PDF 中获取所有 SDF/COS 对象

问题描述

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

最初,我用来PDFDoc加载文档,并遍历页面只是寻找Element类型为e_imageor的对象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() };

这运行正常,但只返回 45 个对象,其中没有一个是我真正感兴趣的图像。

如何简单地从文档中获取所有 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();
    }
}

这种方法的工作原理是它返回了一些图像,但不是全部。对于一些示例文档,它返回所有,对于一些它返回一个子集,而对于一些它根本没有返回。


编辑

仅供将来参考,感谢 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);

标签: c#.net-corepdftronpdfnet

解决方案


如果您想获取 PDF 页面上实际使用的图像(以防 PDF 中碰巧有未使用的图像),那么您将使用此示例代码。此代码将具有包含内联图像的额外好处。 https://www.pdftron.com/documentation/samples/dotnetcore/cs/ImageExtractTest

虽然上述可能很慢,但如果文档有数百或数千页,那么图形上会很复杂。

如您所述,另一种方法是迭代 COS 对象。以下 C# 代码查找所有图像流。请注意,PDF 标准明确规定 Streams 必须是间接对象。所以我认为你可以放心地省略阅读所有直接对象。

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());
                }
            }
        }
    }
}

推荐阅读