Get CGPDFDocumentRef name of document
Asked Answered
E

2

5

Is it possible to retrieve the name of a document from a CGPDFDocumentRef

Eisenhart answered 4/5, 2011 at 10:23 Comment(0)
C
5

By "name of a document", do you mean the document filename or title?

If the document "title" is included in the metadata it can be retrieved like this:

    char *titleKey = "Title";
    CGPDFStringRef titleStringRef;

    CGPDFDictionaryRef info = CGPDFDocumentGetInfo(myDocumentRef);
    CGPDFDictionaryGetString(info, titleKey, &titleStringRef);
    const unsigned char *titleCstring = CGPDFStringGetBytePtr(titleStringRef);

    printf("title: %s", titleCstring);

The other keys are listed in section 10.2 of the PDF 1.7 specification: Adobe PDF Reference Archives

Clarsach answered 6/5, 2011 at 14:8 Comment(2)
Thanks, I actually mean the filename but my guess is, that that is impossible. Do you happen to know the key for retrieving the creation date from the meta-data?Eisenhart
"CreationDate", and there is also a "ModDate".Clarsach
H
2

Here's how to do it in Swift 5:

extension CGPDFDocument {
    var title: String? {
        guard let infoDict = self.info else {
            return nil
        }
        let titleKey = ("Title" as NSString).cString(using: String.Encoding.ascii.rawValue)!
        var titleStringRef: CGPDFStringRef?
        CGPDFDictionaryGetString(infoDict, titleKey, &titleStringRef)
        if let stringRef = titleStringRef,
           let cTitle = CGPDFStringGetBytePtr(stringRef) {
            let length = CGPDFStringGetLength(stringRef)
            let encoding = CFStringBuiltInEncodings.UTF8.rawValue
            let allocator = kCFAllocatorDefault
            let optionalTitle: UnsafePointer<UInt8>! = Optional<UnsafePointer<UInt8>>(cTitle)
            if let title = CFStringCreateWithBytes(allocator, optionalTitle, length, encoding, true) {
                return title as String
            }
        }
        return nil
    }
}

And here’s my understanding of how it works:

First, we check to see if the PDF document has an info dictionary attached. The PDF info dictionary can contain metadata including the title of the document.*

        guard let infoDict = self.info else {
            return nil
        }

If it does, we try to get the title from that dictionary using the CGPDFDictionary API. This API only accepts C types, so we need to perform some conversions to get the Swift String ”Title” represented as a C string.

        let titleKey = ("Title" as NSString).cString(using: String.Encoding.ascii.rawValue)!

The CGPDFDictionaryGetString call takes a pointer to a CGPDFStringRef? variable as its third argument. To convert a Swift reference to a pointer, we prepend it with &. The result of the dictionary lookup can be nil if the title wasn't specified when the PDF was created.

        var titleStringRef: CGPDFStringRef?
        CGPDFDictionaryGetString(infoDict, titleKey, &titleStringRef)
        if let stringRef = titleStringRef,
           let cTitle = CGPDFStringGetBytePtr(stringRef) {

At this point we know there is a title string, however it's not in a usable Swift string, yet. To read the C string from memory (with CFStringCreateWithBytes), we need to know where it starts (pointer) and after how many bytes to stop reading (length). Additionally, we specify that the string should be read using UTF-8 encoding and use the default memory layout. The last item we need is a properly typed reference to the C string. The type of a C string is a pointer to a char, which is represented as a UInt8 in memory. So we end up with Optional<UnsafePointer<UInt8>>.

            let length = CGPDFStringGetLength(stringRef)
            let encoding = CFStringBuiltInEncodings.UTF8.rawValue
            let allocator = kCFAllocatorDefault
            let optionalTitle: UnsafePointer<UInt8>! = Optional<UnsafePointer<UInt8>>(cTitle)

With that information collected, it’s now time to get the Swift String from the C string. Thankfully, CFString is toll-free bridged to Swift’s String, which means we can use the CFStringCreateWithBytes call and simply cast the result to String.

            if let title = CFStringCreateWithBytes(allocator, optionalTitle, length, encoding, true) {
                return title as String
            }
        }
        return nil

*The keys for the values in this dictionary can be found in the Adobe PDF Reference book, TABLE 10.2 "Entries in the document information dictionary" on page 844.

Hindman answered 27/9, 2020 at 23:6 Comment(2)
Could you comment on the answer? This will be helpful to others to understand how it works.Actuary
@WilliamPrigolLopes Thanks for the heads up, I have added an explanation to the best of my knowledge.Hindman

© 2022 - 2024 — McMap. All rights reserved.