Mac OS X での印刷処理

これはドキュメントタイプでは無いアプリケーションで印刷を行おうと戦った結果です。間違っている所は多々あると思いますが、参考になれば幸いです。
まず、Mac OS X での印刷は印刷するビューを作成してそれを NSPrintOperation で指定すれば可能です。言い換えれば印刷するビューを作成しないと印刷出来ない、と言う事になります。
プログラムでは NSView のサブクラスを作成します。「Print」アクションを受け取るとまず現在設定されている用紙の情報を取得します。
NSPrintInfo *pinfo = [NSPrintInfo sharedPrintInfo];
NSSize paperSize = [pinfo paperSize];
で、印刷するビューを作成します。
PrintView *view;
vew = [[PrintView allocWithZone:[self zone]]
initWithFrame:NSMakeRect(
0.0, 0.0, paperSize.width, paperSize.height)
memos:item];
私はメモを印刷するのでここでメモの情報をセットしています。別に後でも良いかも。
印刷するフォントを設定します。
[view setFont:mTextFont];
印刷する対象が既にフォーマットに関する情報を持っているのなら不要だと思います。私の場合素のテキストなので既定のフォントをセットしています。
NSPrintOperation *op = [NSPrintOperation
printOperationWithView:view
printInfo:pinfo];
[op setShowPanels:YES];
[op runOperation];
これで印刷パネルが表示されます。
その後ビューをリリースします。
[view release];
これで本体側の処理は終わりです。

次に印刷されるビューに関して。
最初は生成時に呼ばれる initWithFrame です。
– (id)initWithFrame:(NSRect)frame memos:(ListItem*)ListItem;
ここでは、まず NSView のフレームのセットと印刷する内容を保持する NSTextStorage レイアウトを担当する NSLayoutManager 実際の表示を担当する NSTextContainer をそれぞれ初期化します。
self = [super initWithFrame:frame];
まず NSView のフレームをセットします。 NSView のサブクラスなので親に対してセットします。
次にNSTextStorage を生成します。
mTextStorage = [[NSTextStorage alloc] init];
NSLayoutManager *layoutManager = [[NSLayoutManager alloc] init];
NSLayoutManager を生成します。
[layoutManager setDelegate:self];
NSLayoutManager のデリゲートに自分をセットします。
NSTextContainer *textContainer = [[NSTextContainer alloc] init];
NSTextContainer を生成します。
NSSize tcSize;
tcSize.width = frame.size.width;
tcSize.height = frame.size.height;
NSTextContainer は最初どんな大きさでも表示出来るように可能な最大の大きさを持っていますので、ここでは印刷する1ページの大きさをセットします。
[textContainer setContainerSize:tcSize];
[layoutManager addTextContainer:textContainer];
[textContainer release];
NSTextContainer を NSLayoutManager にセットしリリースします。
[mTextStorage addLayoutManager:layoutManager];
[layoutManager release];
NSLayoutManager を NSTextStorage にセットしてリリースします。
[mTextStorage retain];
NSTextStorage は retain します。drawRect とかで使用するので。

ここで NSTextStorage に印刷するメモの内容をセットしても良いのですが(最初はここでセットしていた)私の場合フォントをセットするので、そのメソッドで行いました。
でもメモのオブジェクトが有るので、それをクラス変数にセットして retain しています。

NSTextStorage に印刷する内容をセットするのですが、NSTextStorage を変更する場合は処理の前に
[mTextStorage beginEditing];
変更が終わった後で
[mTextStorage endEditing];
を実行する必要が有ります。
さらに NSTextStorage にセットする内容は NSAttributedString となります。
特に編集する必要が無い場合は
NSAttributedString *memoAttr = [[NSAttributedString alloc] initWithString:memo];
で大丈夫です。色々とフォントや大きさ、色、等を変更したい場合はここで行って下さい。
[mTextStorage appendAttributedString: memoAttr];
で印刷したい文字列を NSTextStorage にセットします。
ここで印刷したい内容が1ページに収まる場合は特に問題も無く、何もしなくても印刷出来ます。
1ページに収まらない場合は NSLayoutManager に Delegate をセットしたので
– (void)layoutManager:(NSLayoutManager *)aLayoutManager didCompleteLayoutForTextContainer:(NSTextContainer *)aTextContainer atEnd:(BOOL)flag
が呼び出されます。
ここで aTextContainer が nil の場合 NSTextStorage の内容を NSLayoutManager が NSTextContainer に配置していったが一杯になった、と言う事なので initWithFrame でやった様に
NSTextContainer を生成して NSLayoutManager に追加してやります。
NSTextContainer *textContainer = [[NSTextContainer alloc] init];
NSSize tcSize;
tcSize.width = paperSize.width;
tcSize.height = paperSize.height;
[textContainer setContainerSize:tcSize];
[aLayoutManager addTextContainer:textContainer];
[textContainer release];
そして印刷する範囲が広がったのでこの印刷ビューの大きさも大きくします。
NSRect frame = [self frame];
frame.size.height += paperSize.height;
[self setFrame:frame];
で、これが不思議なのですが追加した NSTextContainer のグリフの範囲を取得すると旨く動作します。
NSRange glyphRange = [aLayoutManager glyphRangeForTextContainer:textContainer];
これを実行しないと再度 NSTextContainer が一杯になった時このデリゲートが呼ばれません。
呼ばれないと印刷できない情報が残ってしまいます。
NSLayoutManager のデリゲートにはもう一つ
– (void)layoutManagerDidInvalidateLayout:(NSLayoutManager *)aLayoutManager
が有ります。レイアウトが無効になった時呼び出される、とか。
私は理解出来てませんが、この中では
int glyphNum = [aLayoutManager numberOfGlyphs];
と NSLayoutManager のグリフの数を取得すると旨く動作します。
複数ページ印刷する場合は NSLayoutManager のデリゲートが何度か呼び出されて NSTextContainer を NSLayoutManager に追加する、と言う処理を行います。

実際の1ページの印刷処理は
– (void)drawRect:(NSRect)rect
が担当します。
NSLayoutManager *layoutManager = [[mTextStorage layoutManagers] objectAtIndex:0];
で NSLayoutManager を取得します。今回は NSLayoutManager を1個しか使って無いので NSArray の最初のオブジェクトです。
次に NSTextContainer を取得しますが複数ページの場合 NSTextContainer が複数存在するので印刷対象のページの NSTextContainer を取得します。
NSTextContainer *textContainer = [[layoutManager textContainers] objectAtIndex:page];
取得した NSTextContainer で印刷可能なグリフ範囲を取得します。
NSRange glyphRange = [layoutManager
glyphRangeForTextContainer:textContainer];
で、取得したグリフ範囲を描画します。
[self lockFocus];
[layoutManager drawGlyphsForGlyphRange: glyphRange atPoint: rect.origin];
[self unlockFocus];

以上で、とりあえず印刷は可能です。
今後の課題としては
・デリゲートが追加した NSTextContainer のグリフ範囲を取得しないと続けて呼び出されないのは何故?
・今回は印刷ビューの中でレイアウト処理を行ったが、良いのか?
・先にレイアウトを行って準備出来てから印刷ビューを作成した方が良いのか?

まぁ初めての印刷処理にしては上出来かな、えっ?参考にもならない、すみません。