アプリ上でPDFから画像をぶっこ抜きたい(ネットランナー感)ことってありますよね!?
...ありますよね!?
それ、Core Graphicsを使えばできます。
Core Graphicsについて
Core Graphicsは、UIImageなどの画像を使った処理ではなく、一からオリジナルの画像を描画するために使用されるフレームワークです。
そんなCore Graphicsさんですが、CGPDFDocumentというAPIがあり、PDF内の要素を取得することができます。
実装
なかなかに大変です。
PDFのコンテンツデータにはオペレータと呼ばれる、ストリームに含まれるデータに対する処理方法を示す記号が含まれています。
コンテンツデータを逐次読んでいき、読み込みたい種類のデータを示すオペレータが見つかったら、コールバックを行うAPIがCore Graphicsに用意されています。
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 で公開されています。
あ、悪用厳禁でお願いします(ネットランナー感)。