Получить все объекты SDF / COS из PDF - PullRequest
1 голос
/ 13 февраля 2020

Я пытаюсь получить список всех объектов SDF / COS в документе PDF, используя PDFNet 7.0.4 и netcoreapp3.1. Используя другой анализатор PDF, я знаю, что в этом документе содержится 570 объектов COS, включая 3 изображения.

Изначально я использовал PDFDoc для загрузки документа и перебирал страницы, просто ища Element объекты типа e_image или e_inline_image, но это дало только 2 из 3 изображений. В большом документе это было еще хуже; 0 из ~ 2600 изображений.

Теперь я отступил и пытаюсь выполнить поиск более низкого уровня с помощью 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 из документа?


edit

Вот оригинал 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();
    }
}

Этот вид работал так, что возвращал некоторые изображения, но не все. Для некоторых образцов документов он вернул все, для некоторых он вернул подмножество, а для некоторых он вообще ничего не возвратил.


edit

Только для дальнейшего использования Благодаря ответу Райана, приведенному ниже, мы получили пару хороших чистых методов расширения:

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

1 Ответ

1 голос
/ 14 февраля 2020

Если вы хотите получить изображения, которые фактически используются на странице 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());
                }
            }
        }
    }
}
...