Javaでのプリンタ制御・印刷

ご注意:この文書は古いです。

エスケープシーケンスの送出

 

送出方法とその内部処理

通常の印刷を行う方法とは別に、プリンタ固有のエスケープシーケンス(プリンタコマンド)を送出したい場合がある。あるPrintServiceが与えられたとき、それに対してエスケープシーケンスを送りたい場合は次のようにする。

  PrintService service; // 送出先のプリントサービス
  byte[]bytes; // エスケープシーケンス

  DocFlavor flavor = DocFlavor.BYTE_ARRAY.AUTOSENSE;
  Doc doc = new SimpleDoc(bytes, flavor, null);
  PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet();
  DocPrintJob job = service.createPrintJob();
  job.print(doc, aset);

※エスケープシーケンスを入力ストリームから取得する場合はSimpleDocを以下のようにする。

  InputStream in; // エスケープシーケンス
  DocFlavor flavor = DocFlavor.INPUT_STREAM.AUTOSENSE;
  Doc doc = new SimpleDoc(in, flavor, null);

特にWindowsの場合、上の方法で送信されたバイト列は以下のように処理される。これは通常のWindows用C++プログラムにて、エスケープシーケンスを送出する方法そのものである(VB等でも同様の処理)。
※以下はJRE6.0のソースコードより、

JNIEXPORT jboolean JNICALL
Java_sun_print_Win32PrintJob_startPrintRawData(JNIEnv *env,
                                               jobject peer,
                                               jstring printer,
                                               jstring jobname)
{
  HANDLE      hPrinter;
  DOC_INFO_1  DocInfo;
  LPTSTR printerName = (LPTSTR)JNU_GetStringPlatformChars(env, printer, NULL);
  DASSERT(jobname != NULL);
  LPTSTR lpJobName = (LPTSTR)JNU_GetStringPlatformChars(env, jobname, NULL);
  LPTSTR jname = _tcsdup(lpJobName);
  JNU_ReleaseStringPlatformChars(env, jobname, lpJobName);

  // Start by opening the printer
  if (!::OpenPrinter(printerName, &hPrinter, NULL)) {
    JNU_ReleaseStringPlatformChars(env, printer, printerName);
    free((LPTSTR)jname);
    return false;
  }

  JNU_ReleaseStringPlatformChars(env, printer, printerName);

  // Fill in the structure with info about this "document."
  DocInfo.pDocName = jname;
  DocInfo.pOutputFile = NULL;
  DocInfo.pDatatype = TEXT("RAW");

  // Inform the spooler the document is beginning.
  if( (::StartDocPrinter(hPrinter, 1, (LPBYTE)&DocInfo)) == 0 ) {
    ::ClosePrinter( hPrinter );
    free((LPTSTR)jname);
    return false;
  }

  free((LPTSTR)jname);

  // Start a page.
  if( ! ::StartPagePrinter( hPrinter ) ) {
    ::EndDocPrinter( hPrinter );
    ::ClosePrinter( hPrinter );
    return false;
  }

  // store handle
  jfieldID fieldId = getIdOfLongField(env, peer, HPRINTER_STR);
  env->SetLongField(peer, fieldId, reinterpret_cast<jlong>(hPrinter));
  return true;
}

Windowsのバグ回避

特にWindowsの印刷関連システムはバグが多い。Windows XPでは上記方法でうまくいくが、Windows 2000ではエラーが発生したり(スプールON時)、無視されたりする(スプールOFF時)。
これを避ける方法の一つとして、上記処理の直前に「成功する印刷」を行っておく方法がある。

※XPでも同様の状態になるという報告あり。送信先プリンタの種類による、という可能性あり。

DocPrintJob job = service.createPrintJob();
DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
Doc doc = new SimpleDoc(new Printable() {
 public int   print(Graphics graphics, PageFormat pageFormat, int pageIndex) {
   if (pageIndex == 0) return PAGE_EXISTS;
     return NO_SUCH_PAGE;
   }}, flavor, null);
job.print(doc, null);

DPI値を取得する方法

エスケープシーケンス(プリンタコマンド)では、印刷位置として「ドット位置」を指定しなければならない場合が多い。したがって、事前にプリンタのDPI値を取得しておく必要が生ずる。これは以下のようにする。

    PrinterResolution[]resArray = 
      (PrinterResolution[])target.getSupportedAttributeValues(PrinterResolution.class, null, null);
    if (resArray == null || resArray.length == 0) {
      // 取得不可
      return;
    }
    if (resArray.length != 1) {
       // 複数の解像度をサポートしている
      return;
    }
    int dpiX = resArray[0].getCrossFeedResolution(PrinterResolution.DPI);
    int dpiY = resArray[0].getFeedResolution(PrinterResolution.DPI);

プリンタは複数の解像度をサポートしていることがある。この場合はエスケープシーケンス内でどの解像度を使用するかを指定する必要があると思われる。上記では、ただ一つの解像度の場合のDPI値を取得している。

crossFeedResolutionとは、フィード方向に直角方向の解像度であり、いわゆるX値である。feedResolutionとはフィード方向のY値である。
内部的にはこれらの値はDPHI値として格納されており、これはDPI値の100倍であるが、取得時の引数としてPrinterResolution.DPIを指定することにより、返り値はDPI値となる。