Core GraphicsでPDFから画像をぶっこ抜き

アプリ上でPDFから画像をぶっこ抜きたい（ネットランナー感）ことってありますよね！？

...ありますよね！？

それ、Core Graphicsを使えばできます。

## Core Graphicsについて
[Core Graphics](https://developer.apple.com/documentation/coregraphics)は、UIImageなどの画像を使った処理ではなく、一からオリジナルの画像を描画するために使用されるフレームワークです。
そんなCore Graphicsさんですが、[CGPDFDocument](https://developer.apple.com/documentation/coregraphics/cgpdfdocument)というAPIがあり、PDF内の要素を取得することができます。

## 実装
なかなかに大変です。
PDFのコンテンツデータにはオペレータと呼ばれる、ストリームに含まれるデータに対する処理方法を示す記号が含まれています。
コンテンツデータを逐次読んでいき、読み込みたい種類のデータを示すオペレータが見つかったら、コールバックを行うAPIがCore Graphicsに用意されています。

``` swift
class PDFExtractor{
    private var returnImageData: Array<CFData> = Array<CFData>()

    public func extractImage(filePath: CFURL) -> Array<CFData>?{
        //PDFファイルを読み込む
        guard let pdfDocument: CGPDFDocument = CGPDFDocument(filePath) else { return nil }
        
        //コールバックを登録するために、PDFOperatorTableを生成
        let table: CGPDFScannerRef = CGPDFOperatorTableCreate()!
        
        //コールバックを登録する。"Do"オペレータをスキャンしたらコールバックする
        //https://www.adobe.com/content/dam/acom/en/devnet/pdf/pdfs/PDF32000_2008.pdf
        CGPDFOperatorTableSetCallback(table, "Do", {(scanner: CGPDFScannerRef, info: UnsafeMutableRawPointer?) -> Void in
            let stream: CGPDFContentStreamRef = CGPDFScannerGetContentStream(scanner)
            //画像のファイル名を取得する
            var name: UnsafePointer<Int8>?
            if !CGPDFScannerPopName(scanner, &name) { return }
            
            //ラスタ画像のカテゴリは"XObjext"となる。取得したファイル名を指定する
            guard let object: CGPDFObjectRef = CGPDFContentStreamGetResource(stream, "XObject", name!) else { return }
            
            //画像のstreamを取得する
            var objectStream: CGPDFObjectRef?
            if(!CGPDFObjectGetValue(object, CGPDFObjectType.stream, &objectStream)){ return }
            
            //画像のstreamの種類を判別する
            guard let objectDictionary = CGPDFStreamGetDictionary(objectStream!) else{ return }
            var subType: UnsafePointer<Int8>?
            if(!CGPDFDictionaryGetName(objectDictionary, "Subtype", &subType)) { return }
            if(String(cString: subType!) != "Image") { return }
            
            //streamをCFDataとしてコピー
            var format: CGPDFDataFormat = CGPDFDataFormat.raw
            guard let data: CFData = CGPDFStreamCopyData(objectStream!, &format) else { return }
            
            //クラスのポインタを使って取得した画像を配列に入れる
            let instance = Unmanaged<PDFExtractor>.fromOpaque(info!).takeUnretainedValue()
            instance.returnImageData.append(data)
        })
        
        //ページ毎に画像を取得する
        for i in 1 ... pdfDocument.numberOfPages{
            let page = pdfDocument.page(at: i)
            let stream : CGPDFContentStreamRef = CGPDFContentStreamCreateWithPage(page!)
            
            //取り出した複数の画像を返すために、クラスのポインタを渡す
            let pointer :  UnsafeMutableRawPointer = UnsafeMutableRawPointer(mutating: bridge(obj: self))
            let scanner : CGPDFScannerRef = CGPDFScannerCreate(stream, table, pointer)
            
            //ContentStreamをスキャンする。
            //CGPDFOperatorTableに登録されたオペレータをスキャンすると、コールバックされる
            CGPDFScannerScan(scanner)
            
            //ちゃんと解放しましょう
            CGPDFScannerRelease(scanner)
            CGPDFContentStreamRelease(stream)
        }
        
        CGPDFOperatorTableRelease(table)
        
        return self.returnImageData
    }

    private func bridge<T: AnyObject>(obj: T) -> UnsafeRawPointer{
        return UnsafeRawPointer(Unmanaged.passUnretained(obj).toOpaque())
    }
}
```

## 終わりに
PDFのファイル構造について全くの無知だったのですが、構造自体はシンプルなことがわかりました。
、、が仕様がかなり膨大です。
ちなみにPDFの仕様はhttps://www.adobe.com/devnet/pdf/pdf_reference.html で公開されています。

あ、悪用厳禁でお願いします（ネットランナー感）。