41
iOS でで PDF でででででで でで でで @ponpoko1968

Cocoa勉強会pdf関連

Embed Size (px)

Citation preview

Page 1: Cocoa勉強会pdf関連

iOS での PDF 処理あれこれ

越智 修司

@ponpoko1968

Page 2: Cocoa勉強会pdf関連

自己紹介越智修司

• KLab( くらぶ ) 株式会社

• ソシャゲの会社でソシャゲじゃ無いもの作ってます

• アプリ・サービスのプロトタイピング

• 有名アーティスト・アイドルのファンクラブアプリ開発

• 最近はデータ解析

• python,R など

Page 3: Cocoa勉強会pdf関連

動機• 自炊始めた

• PDF リーダーをいろいろ試してみた

• 要望• 読書内容を残したい・シェア

• evernote

• facebook

• 視力が落ちてきた人 ( つまり自分 ) も読みやすく

Page 4: Cocoa勉強会pdf関連

クリップリーダー• PDF リーダー

Page 5: Cocoa勉強会pdf関連

苦労したこと• メモリ消費

• 目次の抽出

• 文字列の抽出

Page 6: Cocoa勉強会pdf関連

PDF 描画 (1)

• CGContextDrawPDFPage() を使えばOK

• ビットマップコンテクストに描画して永続化すればキャッシュできる

Page 7: Cocoa勉強会pdf関連

PDF 描画 (2)CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL(url);

// ページ番号からページを取得CGPDFPageRef page = CGPDFDocumentGetPage ( pdfDocument, pageNum );

CGContextDrawPDFPage( context, page );CGPDFDocumentRelease( pdfDocument );

Page 8: Cocoa勉強会pdf関連

PDF 描画 (3)

• 注意点

• メモリを消費します。

• しかもページを取得して描画するたびに消費量が増えます。

• 対策

• ページレンダリングの都度 CGPDFDocument を開放して開き直す。→パフォーマンス劣化

• didReceiveMemoryWarning を受け取ったらいったん閉じて再開。

Page 9: Cocoa勉強会pdf関連

「目次」機能

Page 10: Cocoa勉強会pdf関連

Core Graphics での PDF の構造• 階層化されたオブジェクトの集合体

• ページ• フォント• コンテントストリーム

Page 11: Cocoa勉強会pdf関連

Document management — Portable document format — Part 1: PDF 1.7 より

Page 12: Cocoa勉強会pdf関連

PDF 描画 (2)CGPDFDocumentRef pdfDocument = CGPDFDocumentCreateWithURL(url);

// ページ番号からページを取得CGPDFPageRef page = CGPDFDocumentGetPage ( pdfDocument, pageNum );

CGContextDrawPDFPage( context, page );CGPDFDocumentRelease( pdfDocument );

Page 13: Cocoa勉強会pdf関連

Core Graphics での PDF の構造• Document Catalog という辞書から文

書構造を取り出す

• オブジェクト単位の情報はCGPDFDictionary として扱われる

• 配列は CGPDFArray として扱われる場合と、 'First','Next' キーから参照できるリンクリストになっている場合がある( Lisp の car と cdr のようなもの )

• しかも Composite 構造になっている

Page 14: Cocoa勉強会pdf関連

リンクリスト

Next

First

Page 15: Cocoa勉強会pdf関連

Pages

Page 16: Cocoa勉強会pdf関連

ページ 並びの取得CGPDFDictionaryRef catalog=CGPDFDocumentGetCatalog( document_ );CGPDFDictionaryRef pages =NULL;CGPDFDictionaryGetDictionary(catalog, "Pages", &pages);

// 先頭の要素を取得CGPDFArrayRef pagesArray= NULL;CGPDFDictionaryGetDictionary(pages, "Kids", &pagesArray);

int cnt = CGPDFArrayGetCount (pagesArray );for ( int i = 0; i < cnt; i++ ){

const char *typeString;CGPDFDictionaryRef pageDict;CGPDFArrayGetDictionary(pagesArray, i, &pageDict );CGPDFDictionaryGetName(pageDict, "Type", &typeString );if(strncmp("Page",typeString,strlen("Page"))==0 ){

// pageDict オブジェクトへのポインタと、ページ番号をNSDictionary に保存[pageNumDict setValue:[NSNumber numberWithInt:pageNum]

forKey:[NSString stringWithFormat:@"%p",pageDict] ];

}

Page 17: Cocoa勉強会pdf関連

目次構造の取得

Document management — Portable document format — Part 1: PDF 1.7 より

Page 18: Cocoa勉強会pdf関連

PDF 文書構造(1)CGPDFDictionaryRef

catalog=CGPDFDocumentGetCatalog( document_ );CGPDFDictionaryRef outlines=NULL;CGPDFDictionaryGetDictionary(catalog, "Outlines",

&outlines );

// 先頭の要素を取得CGPDFDictionaryRef first = NULL;CGPDFDictionaryGetDictionary(outlines, "First", &first );

// 見出しを取得CGPDFStringRef title;CGPDFDictionaryGetString ( first, "Title", &title );

// 次の章 (cdr 部 ) を取得CGPDFDictionaryRef next = NULL;CGPDFDictionaryGetDictionary(outlines, "Next", &next);

// 小見出し( car 部)を取得CGPDFDictionaryRef children;CGPDFDictionaryGetString ( first, "First", &children);

Page 19: Cocoa勉強会pdf関連

PDF 文書構造(2)// ページオブジェクトを取得CGPDFDictionaryRef page;CGPDFDictionaryDictionary ( first, "D", &title );

// 「 D 」キーがなく、ページオブジェクトを直接参照できない場合CGPDFStringRef dest;CGPDFDictionaryGetString(dict, "Dest", &dest );

Page 20: Cocoa勉強会pdf関連

Document management — Portable document format — Part 1: PDF 1.7 より

Page 21: Cocoa勉強会pdf関連

ページ番号と文書構造のリンク

1

2

3

・・・

n ContentsPages

val key

Page 22: Cocoa勉強会pdf関連

文字列抽出機能

• コンテントストリームのなかから、文字列描画命令部分を取り出す

Page 23: Cocoa勉強会pdf関連

コンテントストリーム

ページ上で表現される一連の描画命令とデータ

Page 24: Cocoa勉強会pdf関連

PDF のオペレータ

• 後置記法

• パラメータは LIFO スタックに積まれる

param1 param2 param3 param4 op

param4

param3

param2

param1

Page 25: Cocoa勉強会pdf関連

PDF の文字列描画

BT % Begin Text/F1 24 Tf % フォント指定

% /F1がフォントを表現するシンボル

1 0 0 1 72 648 Tm % 描画位置の指定

(Hello World) Tj % 文字列描画 -- (と )が引用符

1 0 0 1 72 612 Tm% non-ASCII文字列

<4D53835383568362834E3234837C834383938367> Tj1 0 0 1 72 576 Tm0.5 g % グレイスケール<82BB82EA82F08A44904682C982B582BD82E082CC> TjET % End Text

「 PDF by Hand 」http://www.kobu.com/docs/pdf/pdfxhand.htmより

Page 26: Cocoa勉強会pdf関連

文字列描画オペレータ

• Tj オペレータ

Page 27: Cocoa勉強会pdf関連

文字列抽出

// コールバック関数を設定するCGPDFOperatorTableRef table_;CGPDFOperatorTableSetCallback(table_,

"BT",stringBlockBeginsCallback);CGPDFOperatorTableSetCallback(table_, "ET",

stringBlockEndedCallback);CGPDFOperatorTableSetCallback(table_, "TJ",

stringArrayCallback);CGPDFOperatorTableSetCallback(table_, "Tj", stringCallback);CGPDFOperatorTableSetCallback(table_, "Tf", fontCallback);

// ページに適用CGPDFContentStreamRef contentStream =

CGPDFContentStreamCreateWithPage(page);CGPDFScannerRef scanner = CGPDFScannerCreate(contentStream,

table_, self); // userinfo として self を指定bool ret = CGPDFScannerScan(scanner);

Page 28: Cocoa勉強会pdf関連

文字列抽出コールバックstatic void stringCallback(CGPDFScannerRef inScanner, void

*userInfo){ PDFStringExtractor *zelf = (PDFStringExtractor *)userInfo; CGPDFStringRef string=NULL;

if(CGPDFScannerPopString(inScanner, &string)) {

// ↑LIFO なのでポップする // 座標関連の命令を取り出すときは注意

NSString* s = [zelf stringWithPDFString:string];

Page 29: Cocoa勉強会pdf関連

文字列抽出コールバックstatic void stringCallback(CGPDFScannerRef inScanner, void

*userInfo){ PDFStringExtractor *zelf = (PDFStringExtractor *)userInfo; CGPDFStringRef string=NULL;

if(CGPDFScannerPopString(inScanner, &string)) {

// ↑ 全然文字列じゃない!!!

NSString* s = [zelf stringWithPDFString:string];

Page 30: Cocoa勉強会pdf関連

レンダラの気持ちになって考える

Page 31: Cocoa勉強会pdf関連

CID

• PDF における " 文字列 " は、実際には CID の列であることがある

• CID= グリフ ( 字形 ) を一意に識別するためのID

• CID と文字コードのマッピングは文字列描画に用いるフォントによって異なる

Page 32: Cocoa勉強会pdf関連

フォント指定が重要

BT % Begin Text/F1 24 Tf % フォント指定

% /F1がフォントを表現するシンボル

1 0 0 1 72 648 Tm % 描画位置の指定

(Hello World) Tj % 文字列描画 -- (と )が引用符

1 0 0 1 72 612 Tm% non-ASCII文字列

<4D53835383568362834E3234837C834383938367> Tj1 0 0 1 72 576 Tm0.5 g % グレイスケール<82BB82EA82F08A44904682C982B582BD82E082CC> TjET % End Text

「 PDF by Hand 」http://www.kobu.com/docs/pdf/pdfxhand.htmより

Page 33: Cocoa勉強会pdf関連

CGPDFDictionaryRef pageDict = CGPDFPageGetDictionary(page);CGPDFDictionaryRef resourceDict = NULL;CGPDFDictionaryRef fontDict = NULL;// フォント辞書をスキャンif(CGPDFDictionaryGetDictionary(pageDict, "Resources",

&resourceDict ) ) { if(CGPDFDictionaryGetDictionary(resourceDict, "Font",

&fontDict ) ) {

CGPDFDictionaryApplyFunction(fontDict,enumerateFontsInDictionary,self);

} }

static void enumerateFontsInDictionary(const char *key, CGPDFObjectRef value, void *info) {// フォント情報をキャッシュする

}

Page 34: Cocoa勉強会pdf関連

フォントのエンコーディング情報

• Encoding

• "Identity-H","Identity-V"

• DescendantFont

• /Registry (Adobe)

• /Ordering (Japan-1)

• /Supplement (6)

• CMAP 名

Page 35: Cocoa勉強会pdf関連

CID ファイル :EUC-H の例

100 begincidrange ← 100 個の区間があることを示す<20> <7e> 231 ← cid231 〜 313 は printable ASCII の区間<8ea0> <8edf> 326<a1a1> <a1fe> 633<a2a1> <a2ae> 727<a2ba> <a2c1> 741

.

.

.endcidrange

Page 36: Cocoa勉強会pdf関連
Page 37: Cocoa勉強会pdf関連

課題

• 対応できてないパターンがある

• テキスト領域認識

• 全文検索

Page 38: Cocoa勉強会pdf関連

参考文献• Life is Beautiful (中島聡氏 )

• CloudReaders の開発• PDF レンダリングのメモリ消費の問題

を指摘• 超巨大ページにも対応

• 木下誠氏のマイコミジャーナルの記事• Core Text を用いた CID→Unicode の簡便な解決案が提示されています

• http://news.mynavi.jp/column/iphone/039/index.html

Page 39: Cocoa勉強会pdf関連

参考文献 (2)• 通称フグ本

• CID などアドビ社の多国語対応の情報

• 鈍器としても使えます

Page 40: Cocoa勉強会pdf関連

参考文献 (3)• もっと早く出ていれ

ば。。。

Page 41: Cocoa勉強会pdf関連

PDF Voyeur

https://github.com/below/PDF-Voyeur.git