SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Javaの標準機能だけで実現する帳票印刷

プリンタの検索と選択、そして特に長形3号封筒への印刷

Javaの標準機能だけで実現する帳票印刷 第3回

PrinterJobでプリンタ選択印刷

 作成したPrinterSupportedクラスを使って、実際に印刷する方法を説明します。

 最初に「PrinterJobでの印刷」の項目で紹介した方法は次のとおりでした。例として3部印刷を使うのはやめにして、横置きにしています。

PrinterJobを使った一般的な印刷の例
Printable pable = new SomethingPrintable00();
//用紙サイズなどの指定をrqsetに集約
PrintRequestAttributeSet rqset = new HashPrintRequestAttributeSet();
rqset.add(OrientationRequested.LANDSCAPE);
rqset.add(MediaSizeName.ISO_A3); //A3用紙
rqset.add(new MediaPrintableArea(
   10.1f, 10.3f, 276.7f, 399.3f,
   MediaPrintableArea.MM));//左余白,上余白,描画幅,描画高,単位
//このrqsetを指定してダイアログ提示と印刷
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPrintable(pable);
if (pj.printDialog(rqset)) {
    try { pj.print(rqset); }
    catch (PrinterException e) {
        System.err.println(e);
    }
}

 PrinterSupportedクラスを使うと、こうなります。

PrinterSupportedクラスを使った印刷の例
Printable pable = new SomethingPrintable00();
PrintRequestAttributeSet rqset = new HashPrintRequestAttributeSet();
rqset.add(OrientationRequested.LANDSCAPE);
PrinterSupported supp = new PrinterSupported();
float x=297f;
float y=420f; //A3
PrintService sv = supp.printerSetTo(x,y,rqset);
if (sv !=null) {
    PrinterJob pj = PrinterJob.getPrinterJob();
    pj.setPrintable(pable);
    try {
        pj.setPrintService(sv);
        if (pj.printDialog(rqset)) pj.print(rqset);
    }catch (PrinterException e) {
        System.err.println(e);
    }
}   

 用紙の幅と高さをmmで指定して、PrinterSupportedクラスのprinterSetTo()メソッドを呼び出します。MediaPrintableAreaによる余白の指定は、普通の帳票なら10mm程度でOKなのでprinterSetTo()にまかせてしまいます。

 あまり知られていないのは、pj.setPrintService(sv)ではないでしょうか。PrinterJobのインスタンスにもあとからプリンタ指定ができます。

 printDialog()を残してあります。プリンタ選択は終わっていますから、なくしても構いません。印刷の最終確認のつもりで置いています。

プリンタ選択+PrinterJobの印刷プログラム例

 前項の方法を使って実際に印刷するプログラムを紹介しておきます。

 もちろん、このプログラムの実行には、PrinterSupported.javaが必要です。

SomethingPrintable02.java
import java.awt.*;
import java.awt.geom.*;
import java.awt.print.*;
import static java.awt.Font.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
import java.time.*;
import java.time.format.*;

/** SomethingPrintable02.java 
    PrintRequestAttributeSet と
    PrinterSupportedクラスを使って選択したプリンタ(と用紙)と
    PrinterJobを使う帳票印刷の典型例
    起動時の引数で長形3号、葉書、角形2号、A3、A4、角形6号、洋形6号
    の用紙に印刷できます。プログラム中のX,Yの値を変更すれば
    他の用紙にも印刷可能です。
    コメント行の変更で縦置き、横置きも変更できます
    2019-06-03 adachi
*/
public class SomethingPrintable02 implements Printable {
    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) {
        if (pageIndex != 0) return NO_SUCH_PAGE;
        Graphics2D g2 = (Graphics2D)g;
        float mm2pt = 72/25.4f;
        float x0 = 0.0f*mm2pt;
        float y0 = 10.5f*mm2pt;
        float p  = 1.0f*mm2pt;
        float h3 = 3.0f*mm2pt;
        float h5 = 5.0f*mm2pt;
        float h7 = 7.0f*mm2pt;
        float h10 = 10.0f*mm2pt;
        g2.setStroke(new BasicStroke(0f));
        Line2D line = new Line2D.Float();
        line.setLine(x0, y0, x0+50.0f*mm2pt, y0);
        g2.draw(line);
        float x,ye;
        for(int i=0;50>=i;i++){
            x  = x0 + p*i;
            ye = (i%5==0) ?h5:h3;
            ye = (i%10==0)?h7:ye;
            line.setLine(x, y0, x, y0+ye);
            g2.draw(line);
        }//for
        g2.setFont(new Font(SANS_SERIF, PLAIN, 9));

        double ix = pf.getImageableX();
        double iy = pf.getImageableY();
        double iw = pf.getImageableWidth();
        double ih = pf.getImageableHeight();
        Rectangle2D.Double rectg = new Rectangle2D.Double(ix, iy, iw, ih);
        g2.draw(rectg);
        g2.setPaint(Color.RED);
        rectg = new Rectangle2D.Double(ix+1, iy+1, iw-2, ih-2);
        g2.draw(rectg);

        g2.setPaint(Color.BLACK);
        float yptch = 5.0f*mm2pt;
        float xp = (float)ix + 2f*mm2pt;
        float yp = y0+h5 + yptch;
        int ct = 1;
        double pt2mm = 25.4/72;
        String sfmt = "%s:%5.1f %s:%5.1f";
        double pwmm = pf.getWidth()*pt2mm;
        double phmm = pf.getHeight()*pt2mm;
        double ixmm = ix*pt2mm;
        double iymm = iy*pt2mm;
        double iwmm = iw*pt2mm;
        double ihmm = ih*pt2mm;
        String whx = String.format(sfmt,"W",pwmm,"H",phmm);
        g2.drawString(whx, xp, yp+yptch*ct++);
        whx = String.format(sfmt,"L",ixmm,"R",pwmm-ixmm-iwmm);
        g2.drawString(whx, xp, yp+yptch*ct++);
        whx = String.format(sfmt,"T",iymm,"B",phmm-iymm-ihmm);
        g2.drawString(whx, xp, yp+yptch*ct++);
        whx = String.format(sfmt,"w",iwmm,"h",ihmm);
        g2.drawString(whx, xp, yp+yptch*ct++);
        int ori = pf.getOrientation();
        g2.drawString("0:LAND 1:PORT 2:RevL =[ "+ori+" ]", xp, yp+yptch*ct++);
        String muki = "Unknown";
        if (ori==PageFormat.LANDSCAPE) muki = "LANDSCAPE";
        if (ori==PageFormat.PORTRAIT) muki = "PORTRAIT";
        if (ori==PageFormat.REVERSE_LANDSCAPE) muki = "REVERSE_LANDSCAPE";
        g2.drawString(muki , xp, yp+yptch*ct++);
        g2.drawString(System.getProperty("os.name"), xp, yp+yptch*ct++);
        LocalDateTime d = LocalDateTime.now();
        DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        g2.drawString(d.format(f), xp, yp+yptch*ct++);
        
        x0 = 50.5f*mm2pt;
        y0 = 25.5f*mm2pt;
        g2.setColor(Color.RED);
        line.setLine(x0, y0, x0+h5, y0);
        g2.draw(line);
        g2.setColor(Color.ORANGE);
        line.setLine(x0, y0, x0, y0+h5);
        g2.draw(line);
        g2.setColor(Color.GREEN);
        line.setLine(x0, y0, x0-h5, y0);
        g2.draw(line);
        g2.setColor(Color.BLUE);
        line.setLine(x0, y0, x0, y0-h5);
        g2.draw(line);
        String sfmt2 = "交点は X:%5.1f Y:%5.1f";
        whx = String.format(sfmt2,x0/mm2pt,y0/mm2pt);
        g2.drawString(whx, x0-h5, y0+h7);
        g2.setColor(Color.BLACK);

        return PAGE_EXISTS;
    }

    public static void main(String[] args) {
        boolean DEBUG = true;
        int job = 4;
        float x=210f;
        float y=297f;
        if (args.length==0) {
           System.out.println("0:chou3 1:hagaki 2:kaku2 3:A3");
           System.out.println("4:A4    5:kaku6  6:you6 ");
       }
        if (args.length>0) job = Integer.parseInt(args[0]);
        if(job==0) {x=120f; y=235f;} //chou_3
        if(job==1) {x=100f; y=148f;} //hagaki
        if(job==2) {x=240f; y=332f;} //kaku_2
        if(job==3) {x=297f; y=420f;} //A3
        if(job==4) {x=210f; y=297f;} //A4
        if(job==5) {x=162f; y=229f;} //kaku_6
        if(job==6) {x=98f; y=190f;} //you_6
        Printable pable = new SomethingPrintable02();
        PrintRequestAttributeSet rqset = new HashPrintRequestAttributeSet();
        //rqset.add(OrientationRequested.LANDSCAPE);
        //rqset.add(OrientationRequested.REVERSE_LANDSCAPE);
        PrinterSupported supp = new PrinterSupported();
        PrintService sv = supp.printerSetTo(x,y,rqset);
        if (sv !=null) {
            if(DEBUG){
                System.out.println(">> printer:"+sv.getName());
                Media med = (Media)rqset.get(Media.class);
                System.out.print(">> media  :"+med.toString());
                OrientationRequested ori = 
                    (OrientationRequested)rqset.get(OrientationRequested.class);
                System.out.println((ori!=null)?" :"+ori.toString():" :PORTRAIT");
            }
            PrinterJob pj = PrinterJob.getPrinterJob();
            pj.setPrintable(pable);
            try {
                pj.setPrintService(sv);
                if (pj.printDialog(rqset)) pj.print(rqset);
            }catch (PrinterException e) {
                System.err.println(e);
            }
        }
    }
}

実行結果

 まず、Windows 10から葉書で印刷したものです。印刷内容はSomethingPrintable01.javaとOriTest.javaを混ぜたようなものになっています。葉書にしたのは掲載しやすいからです。

葉書への印刷
葉書への印刷(Windows 10)

 端末(PowerShellのウインドウ)にでる出力は次のようになります。if(DEBUG){...}部分のものです。

実行時の端末表示(Windows)
PS Z:\java\print02w> java SomethingPrintable02 1
>> printer:Canon MP493 series Printer
>> media  :japanese-postcard :PORTRAIT

 GUIの流れを書いておきます。

 PrinterSupportedクラスからのダイアログがでます。

PrinterSupportedクラスからのダイアログ(Windows 10)
PrinterSupportedクラスからのダイアログ(Windows 10)

 選択して[OK]すると、もしもサイズに選択肢があれば用紙を選ぶ画面になります。1つしか選択肢がないとスキップします。ここで用紙サイズ、余白がPrintRequestAttributeSetにaddされます。このプログラムがプリンタと属性に責任を持つのはここまでです。

 次にこのプリンタの他の属性が、SomethingPrintable02のmainからのダイアログで確認できます。

 これは、pj.printDialog(rqset)で出しているものです。

プリンタの確認(Windows 10)
プリンタの確認(Windows 10)

 「ページ設定」のタブも確認します。余白も伝わっています。

用紙サイズと余白の確認(Windows 10)
用紙サイズと余白の確認(Windows 10)

 printDialog()になってからの変更も可能ですが、以下の注意を踏まえてください。

 API仕様ドキュメントによると、プリンタや用紙サイズなどをダイアログ中で変更すると属性値がコピーされ、特定の属性値をサポートしないプリンタをユーザーがブラウズする場合、コピーされる新規値として、そのサービスのデフォルトが使用されるとなっています。つまり葉書をサポートしないプリンタに一度移ると、A4などに変更になってしまいます。[印刷]をクリックするタイミングでコピーが設定値として適用されるようです。

長形3号封筒への印刷

 今回のPrinterSupportedクラスを使って長形3号封筒などへの印刷が可能になりました。

印刷見本
長形3号封筒への印刷(Windows 10)
長形3号封筒への印刷(Windows 10)

 Linuxの場合はただ用紙サイズの指定が変わるだけですが、Windows+MP493プリンタの場合には次のような現象が生じます。

 印刷開始直前に次のようなダイアログがでます。

MP493の封筒の向き
MP493の封筒の向き(Windows 10)

 つまり、この手の封筒は底の方を先にして入れるように指示されます。これは理にかなったことです。もし、頭から紙送りされた場合、封の蓋の部分は用紙の大きさには含まれませんから、正しい位置に印刷されません。ここを折った状態で印刷すればいいのですが、面倒な上に紙送りでトラブルの元になる可能性が大です。

 Linuxではこれは自分で解決しなければならず、印刷するデータを180度回転させることで実現していました。Windowsで自動でやってくれるのは良いことではありますが、OSを判断して動作を変える必要があります。

 一応、印刷するデータを180度回転させる例を示しておきます。次に示すのは、「SomethingPrintable02.java」 のprint()メソッドの変更です。 変更は現在のAffineTransformをoldに格納しておき、rotate()メソッドで回転、描画が終わったらoldを戻しておきます。このプログラムは1枚しか印刷しないので、oldに関わる2行は不要かもしれませんが、今後のために書いておきます。

180度回転で描画するための SomethingPrintable02.java の変更部分
public int print(Graphics g, PageFormat pf, int pageIndex) {
    if (pageIndex != 0) return NO_SUCH_PAGE;
    Graphics2D g2 = (Graphics2D)g;
    AffineTransform old = g2.getTransform();
    g2.rotate(Math.PI,pf.getWidth()/2,pf.getHeight()/2);
    /*
     ここで今までどおりの描画を行う
	*/
    g2.setTransform(old); //rotate
    return PAGE_EXISTS;
}

次のページ
DocPrintJobでの印刷

この記事は参考になりましたか?

Javaの標準機能だけで実現する帳票印刷連載記事一覧

もっと読む

この記事の著者

安達 順一(アダチ ジュンイチ)

私立高校に理科・情報の教員として勤めていました。Linuxサーバー/クライアントの授業システムを作り、移動プロファイルで運用していました。教員用にもLinuxサーバーを用意し成績処理プログラムを書きました。情報の学校設定科目ではウェブページ制作とjavaのプログラミングの初歩の授業を作りました。情報...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/11671 2020/06/22 11:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー