プリンタ出力


プリンタに印刷する

昔、少年ビル・ゲイツが、学校のコンピュータを使って作った三目並べのプログラムは
一度、コンピュータと対戦するのにお昼休みを使ってしまうほど時間がかかったといいます
今のコンピュータや、人間が相手であれば、それほど時間はかからないでしょう

なぜ、少年時代のゲイツ氏の三目並べはこれほど時間がかかったのか
それは、コンピュータが今の時代のそれよりも処理速度が遅いという原因以上に
ディスプレイがついていなかったということが最大の原因だといえるでしょう
この時代には、一般的なコンピュータの出力装置はプリンタだったのです

BASIC 言語も C 言語も、Java 言語も
標準出力に文字列を出力する時には Print という言葉を使います
(Microsoft の最新の言語 C# はこれを打ち破り、Write という言葉を使いましたが…)

ディスプレイに文字を描画させるのに Print という言葉は奇妙です
Write、TextOut、DrawText、DrawString 等の方が、適切と思えます
この Print という言葉は、当時、プリンタが一般的な出力装置だったことを証明する
歴史的背景を物語る「現代に残る過去の習慣」の一つであると言えるでしょう

しかし、時代がディスプレイ標準となってもプリンタの重要性は変わりません
コンピュータのワードソフトで書いた文書を印刷することを望む人がいるでしょうし
あるいは、CG ソフトで描いた芸術作品を印刷したいと思うかもしれません
残念ながら、「音を印刷」することはできませんが
音楽家であれば、音楽の波形データを印刷したいと思うかもしれません

もっとも筆者は、近い将来、全ての人が胸ポケットに小型コンピュータを入れて
高速無線ネットワークで、全ての文字、画像データを印刷することなく
一瞬で互いのコンピュータに送信して、高速情報交換ができる時代が来ると願っています

筆者の希望はともかく、様々な理由から、あなたの作るアプリケーションは
クライアントウィンドウに表示する重要な情報を、プリンタに印刷する手段を提供するべきです

プリンタに文字や図形を描画する場合も、これまで同様に GDI 関数を使います
文字を印刷したいのであれば TextOut() を、長方形なら Rectangle() を呼び出せば良いでしょう
ただし、これらの関数に渡すのはプリンタのデバイスコンテキストでなければなりません

プリンタのデバイスコンテキストは CreateDC() 関数が返します
この関数の、第2引数にプリンタの名前を指定します
プリンタの名前は、印刷のプロパティなどで知ることができるでしょう

もちろん、文書を印刷しようとした時に、ダイアログを表示して
"印刷するプリンタの名前を入力してください" というのはナンセンスです
インターネットの接続もわからない親父が使っていたら、理解できるか不安です
日本語で "エプソン" なんて入れられた日には、一生、印刷できません

しかし、接続されている、有効なプリンタの名前を得る方法は後記します
とりあえず、この場では CreateDC() の第2引数に直接プリンタ名を指定してください
この時、第1引数は NULL でかまいません

そして、この関数が返したデバイスコンテキストを使って
印刷を開始するために StartDoc() 関数を用います

int StartDoc(HDC hdc , CONST DOCINFO *lpdi);

hdc には、印刷ジョブのデバイスコンテキストのハンドルを指定します
lpdi は、DOCINFO 構造体へのポインタを指定します
この構造体に、文書ファイルと出力ファイルの名前を入れておきます
関数が成功すれば 0 よりも大きなジョブIDの値、そうでなければ 0 以下が返ります

DOCINFO 構造体は次の様に定義されています
typedef struct {    // di 
    int     cbSize; 
    LPCTSTR lpszDocName; 
    LPCTSTR lpszOutput; 
    LPCTSTR lpszDatatype;
    DWORD   fwType;
} DOCINFO;
cbSize には、この構造体のサイズを指定します
lpszDocName は、ドキュメントの名前を、
lpszOutput は、出力ファイルの名前を表す文字列へのポインタを指定します
lpszOutput が NULL であれば、StartDoc() に渡す hdc で指定されるデバイスに出力します

lpszDataType には、印刷ジョブを記録するためのデータ型を表す文字列
fwType は、印刷ジョブの追加情報を指定します
ここには 0 または DI_APPBANDING を指定します
バンド処理アプリケーションでは、DI_APPBANDING を指定するべきです

StartDoc() 関数を呼び出すと、デバイスコンテキストのプリンタのドライバを読み込みます
初期化と印刷の準備を、デバイスドライバに通知します
プリンタは、通常バンディングと呼ばれる技術を採用しています
これは、1ページを「バンド」と言う小さい単位に分割し、プリンタに出力するというものです

StartDoc() で出力の準備ができたら、次は最初のページを通知します
新しいページを知らせるには StartPage() 関数を使います

int StartPage(HDC hDC);

hDC には、印刷ジョブのデバイスコンテキストを指定します
成功すれば 0 よりも大きい値、失敗すれば 0 よりも小さな値が返ります

これで、アプリケーションは、デバイスコンテキストに描画することができます
この後、1ページ分の印刷するべきデータを、GDI 関数で描画してください
ただし、ディスプレイと異なり、リアルタイムでプリンタがこれを描画するわけではありません
実は、この時点でデバイスコンテキストはメタデータとなっているのです

私たちは、メタデータとして描画したい図の情報を指定し
最後に、ドライバはメタデータを再生して、バンドごとにクリッピングする仕組みになっています
メタデータに1ページ分の書き込みが終われば EndPage() を呼び出します

int EndPage(HDC hdc);

hdc には、印刷ジョブのデバイスコンテキストを指定します
成功すれば 0 よりも大きな値、失敗すれば 0 よりも小さな値が返ります

アプリケーションが2ページ目を書きたい場合、EndPage() を呼び出してページを終了させ
再び StartPage() を呼び出せば、新しいページを描くことができます

全ての作業が終わり、印刷ジョブを終了する時は EndDoc() を呼びます
この関数は、ジョブを終了させ印刷ドライバに印刷の開始を知らせます
通常、この関数を呼び出した後に、実際の印刷が始まります

int EndDoc(HDC hdc);

hdc には、印刷ジョブのデバイスコンテキストを指定します
成功すれば 0 よりも大きな値、失敗すれば 0 よりも小さな値が返ります

これが、印刷までの基本的な流れです
プリンタの情報は GetDeviceCaps() で取得することができるでしょう
#include <windows.h>

#define TITLE TEXT("Kitty on your lap")

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow ) {
	DOCINFO diInfo = {0};
	HDC hdc;

	diInfo.cbSize = sizeof (DOCINFO);
	diInfo.lpszDocName = TITLE;

	if (!(hdc = CreateDC(NULL , lpCmdLine , NULL , NULL))) {
		MessageBox(NULL , 
			TEXT("DCが得られません") , lpCmdLine , MB_OK);
		return -1;
	}
			
	if (StartDoc(hdc , &diInfo) > 0 && StartPage(hdc) > 0) {
		SetMapMode(hdc , MM_LOMETRIC);
		Ellipse(hdc , 0 , 0 , 500 , -500);
		TextOut(hdc , 0 , 0 , TITLE , lstrlen(TITLE));

		EndPage(hdc);
		EndDoc(hdc);
	}
	DeleteDC(hdc);
	return 0;
}
このプログラムは、"Kitty on your lap" という文字列と円弧をプリントします
印刷するプリンタは、コマンドライン引数で指定したプリンタになります
例えば、筆者の場合は "EPSON PM-670C" と入力して印刷しています(古い…)

EndDoc() を呼び出した後、プリンタにデータを送出してプリントするのはドライバの役目です


StartDoc()

int StartDoc(HDC hdc , CONST DOCINFO *lpdi);

印刷ジョブを開始します

hdc - 印刷ジョブのデバイスコンテキストのハンドルを指定します
lpdi - DOCINFO 構造体へのポインタを指定します

戻り値 - 成功すれば 0 よりも大きなジョブIDの値、失敗すれば 0 以下

StartPage()

int StartPage(HDC hDC);

新しいページを開始します

hDC - 印刷ジョブのデバイスコンテキストを指定します

戻り値 - 成功すれば 0 よりも大きい値、失敗すれば 0 よりも小さな値

EndPage()

int EndPage(HDC hdc);

1ページ分の書き込みを終了したことを通知します

hdc - 印刷ジョブのデバイスコンテキストを指定します

戻り値 - 成功すれば 0 よりも大きい値、失敗すれば 0 よりも小さな値

EndDoc()

int EndDoc(HDC hdc);

印刷ジョブを終了します

hdc - 印刷ジョブのデバイスコンテキストを指定します

戻り値 - 成功すれば 0 よりも大きな値、失敗すれば 0 よりも小さな値



前のページへ戻る次のページへ