javaで帳票印刷 印刷プレビュー

今まで作ってきた帳票印刷の構造

implements Printable したクラスのpaint()メソッドに文字と罫線の描画を行う部分を書き、main()から用紙を指定して印刷しています。説明のために極力単純化しています。

データは将来の拡張のためmain()で用意してコンストラクタで渡すようにしています。

Tsuuchi.javaの骨組み

public class Tsuuchi implements Printable {
    String[] kams;
    int[] tans, tens;
    public Tsuuchi(String[] kams, int[] tans, int[] tens){
        //受け取ったデータを自分の変数に代入....⇨this.kams...
    }
    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) {
        if (pageIndex != 0) return NO_SUCH_PAGE;

         //描画の本体 (均等割付、罫線....)

        return PAGE_EXISTS;
    }

    public static void main(String[] args) {
        //データの準備 ....⇨kams,tans,tens
        //用紙の指定などのAttributeSetの用意 ....⇨reqset
        //Printableなインスタンスをセット
        pj.setPrintable(new Tsuuchi(kams,tans,tens));  //printable instance
        //ダイアログを出して印刷
        if (pj.printDialog( reqset )) {
            try { pj.print( reqset ); }
            catch (PrinterException e) {
                System.err.println(e);
            }
        }
    }
}

これだけ追加すればプレビューができる

描画部分を独立したメソッドに移動してprint()から呼び出すようにします。extends JPanelを加えて、paintComponent()メソッド内からも描画のメソッドを呼び出すようにします。JPanelを表示すればそれがプレビューになります。詳しい説明はプログラム構成の後に書きます

プレビューのために追加したものは、一切省略していません。これだけでプレビューはできます。

TsuuchiPrev.javaの骨組み

import javax.swing.*;  //追加します

public class TsuuchiPrev extends JPanel implements Printable { //①
    String[] kams;
    int[] tans, tens;
    public TsuuchiPrev(String[] kams, int[] tans, int[] tens){
        //受け取ったデータを自分の変数に代入....⇨this.kams...
        setBackground(Color.white);
        setPreferredSize(new Dimension(595,841));  //⑥
    }
    @Override
    public void paintComponent(Graphics g){  //②
        super.paintComponent(g);
        drawPage(g);
    }
    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) {
        if (pageIndex != 0) return NO_SUCH_PAGE;
        drawPage(g);  //③
        return PAGE_EXISTS;
    }
    public void drawPage(Graphics g){  //④

         //描画の本体 (均等割付、罫線....)

    }
    public static void main(String[] args) {
        //データの準備 ....⇨kams,tans,tens
        //用紙の指定などのAttributeSetの用意 ....⇨reqset
        //Printableなインスタンスをセット
        TsuuchiPrev prevPrintable = new TsuuchiPrev(kams,tans,tens);  //⑤
        String[] goOrNot = {"印刷","中止"};
        int rev = JOptionPane.showOptionDialog(
                null, prevPrintable,
                "印刷プレビュー",
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.PLAIN_MESSAGE,
                null,goOrNot,goOrNot[0]
        );
        //rev:"印刷":0,"中止":1
        if (rev!=0) System.exit(0);

        pj.setPrintable(prevPrintable);  //printable instance
        //ダイアログを出して印刷
        if (pj.printDialog( reqset )) {
            try { pj.print( reqset ); }
            catch (PrinterException e) {
                System.err.println(e);
            }
        }
    }
}

追加部分の詳しい解説

① extends JPanel を加えます
コンストラクタに setBackground(Color.white); を置いたのはダイアログの中の「紙」の部分だけ白くしたかったからです。なくても問題ありません。
② paintComponent()メソッドを加えます
JPanelがもともと持っているものをオーバーライドして自身にプレビュー画面を描画します。このようにしておくとウィンドウの上下関係が変わったりしてJPanelの再描画が必要になった時に自動的に描き直されます。
画面を描画は④のdrawPage()で行います。
③ print()メソッドの中にあった描画の本体 (均等割付、罫線....)をそっくりdrawPage()に移動し、これを呼び出すだけにします
印刷の時に呼び出されるのがprint()、プレビューで呼び出されるのがpaintComponent()で、内容を共通にできるというわけです。
④ そのdrawPage()です。
描画の本体 (均等割付、罫線....)です。print()メソッドの中にあったのと同じです。
⑤ 印刷の前にダイアログで表示します
PrinterJobのインスタンスに Printableなインスタンス(prevPrintable)を渡す前に、オプションダイアログを使って表示します。prevPrintableはJPanelを継承しているのでオプションダイアログを使って表示できます。
選択肢を"印刷","中止"にしてわかりやすくするためオプションダイアログにしました。印刷を選択すると、これまでどおり、PrinterJobのインスタンスに Printableなインスタンス(prevPrintable)を渡して印刷のダイアログに進みます。
⑥ JPanelの大きさの希望値をセットします
これがないと、小さなダイアログが出るだけになってしまいます
drawPage()内ではA4の用紙上の位置をポイント(1/72インチ)単位で指示します。つまり位置の最大値はポイントで表したA4の大きさになります。それが 595x841です。これより大きくてもこの範囲にしかなりませんし(drawPage()内でそう指示していますので)、小さいとはみ出した部分が失われて左上の部分だけ表示されます。
拡大や縮小、スクロールなどはこれからの追加課題です。

実行結果

クリックすると実物大になります。

不満

とても簡単にプレビューができましたが、不満もあるでしょう

まず解像度です。それぞれの枠の中にデータが入ることさえわかればいいという要求の場合もあるかもしれませんが、自由に拡大したりスクロールしたりと細かな所まで確認したいかも知れません。

レンダリングヒントを入れることで多少改善されますが、もともと72dpiからくる595✕841が解像度となっているからです。

JOptionPane.showOptionDialog()を使っているので、ウィンドウの拡大ができません。ウィンドウを拡大すれば内容も拡大されるかどうかは、作り方によりますが、showOptionDialog()ではどちらにせよ無理です。

また、GUIでないプログラムで突然showOptionDialog()を使うことで、このDialogには親フレームがありません。他のウィンドウに隠れてしまった時に呼び出せなくなることがあります。使用例ならDialogでなくてFrameを使い、そこにJPanelを入れるのが順当ではないかといえばそのとおりですが、通知表作成時の状況から言えばメインのパネルから学期などを選択して、プレビューという流れなので、とりあえずダイアログだったわけです。

拡大にはshowOptionDialog()をやめて

Dialogをマウスで拡大できるようにするにはAPIにあるように「JDialog インスタンスで setResizable を呼び出す」ようにします。具体的には、showOptionDialog()をやめてJOptionPaneのインスタンスを作り、さらにcreateDialog()でDialogを作って setResizable します。選択した値はJOptionPaneのインスタンスからgetValue()で取り出します。

        TsuuchiPrev prevPrintable = new TsuuchiPrev(kams,tans,tens);  //⑤

        String[] goOrNot = {"印刷","中止"};
        JOptionPane pane = new JOptionPane(
                                   prevPrintable, // scrollPane, 
                                   JOptionPane.PLAIN_MESSAGE, 
                                   JOptionPane.DEFAULT_OPTION,
                                   null,goOrNot,goOrNot[0]
                               );
        //JDialog dialog = pane.createDialog( null, "印刷プレビュー" );
        JDialog dialog = pane.createDialog("印刷プレビュー" );
        dialog.pack();
        dialog.setResizable(true);
        dialog.setVisible(true);
        String svalue = (String)pane.getValue();
        if (svalue==null || !svalue.equals("印刷")) System.exit(0);

        pj.setPrintable(prevPrintable);  //printable instance

これだけでは枠だけ大きくなりますが、中身が拡大されません。

scale()で描画プログラムをそのままに拡大ができます。このメソッドはGraphic2Dのメソッドなので、Graphic2Dへのキャストを先にする必要があります。それに伴い、print()にも同じキャストを書いてdrawPage()の呼び出しをGraphic2Dのインスタンスを引数に変更します。(いままではdrawPage()内でキャストしていました)

拡大率はgetWidth()でpaintComponent()が呼び出された時点の横幅がわかりますから、これをいままでの595で割り算して算出します。両方intなので整数化されないよう595をdouble扱いにします。

    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        double scale = getWidth()/595d;
        g2.scale(scale,scale);
        drawPage(g2);
    }

実行結果 その2 拡大可に

スクロールも

        TsuuchiPrev prevPrintable = new TsuuchiPrev(kams,tans,tens);  //⑤
        JScrollPane scrollPane = new JScrollPane(prevPrintable); 
        String[] goOrNot = {"印刷","中止"};
        JOptionPane pane = new JOptionPane(
                                   scrollPane, 
                                   JOptionPane.PLAIN_MESSAGE, 
                                   JOptionPane.DEFAULT_OPTION,
                                   null,goOrNot,goOrNot[0]
                               );

必要になるとスクロールバーが出ますが、ちょっと変です。一番下までスクロールしても全部見えません。

JScrollPaneのViewPortの大きさに合わせてJPanelの大きさ(getWidth()とgetHeight()の値)がそれぞれ変化します。最小がPreferredSizeの値になります。getWidth()だけに合わせて描画の拡大が起こるようにしましたから、横長のViewPortになるとJPanelもそれに合い、下が切れます。スクロールバーが出るのはPreferredSizeの値より小さくなった時ということのようです。

PreferredSizeを変更することが必要です。

紙の大きさ指定から見直した2つめのバージョン

変数を整理するとわかりやすくなりました。振動の問題はありますが、表示の遅れはありません。

public class TsuuchiPrev extends JPanel implements Printable {
    String[] kams;
    int[] tans, tens;
    double a4longside  = 297d*72/25.4; // 841.88;
    double a4shortside = 210d*72/25.4; // 595.27;
    double pwidth = a4shortside;  //用紙の横幅。用紙を変更する時はここ
    double pheight = a4longside;  //用紙の縦幅
    double wZh = pwidth/pheight;  //横÷縦
    Dimension prevsize;   //previewSize preferredSizeで使用
    double scale;         //Graphics2D#.scale()に使用
    public TsuuchiPrev(String[] kams, int[] tans, int[] tens){
        this.kams = kams;
        this.tans = tans;
        this.tens = tens;
        setBackground(Color.white);
        prevsize = new Dimension(); //(int)pwidth,(int)pheight);
        prevsize.setSize(pwidth,pheight); //newではintしか認められないがsetはdoubleも許容される
        setPreferredSize(prevsize);       //これは初期値 (new Dimension(595,841)におなじ)
    }

paintComponent()に来るたびにウィンドウの大きさからJPanelのpreferredSizeを指定し直す。

ウインドウの大きさは、正確に言うとJScrollPaneのJViewPortの大きさ。この窓からJPanelを覗いていると考える。

    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        //thisがJPanel、parentはviewportになる
        int viewpw = getParent().getWidth();
        int viewph = getParent().getHeight();
        if((double)viewpw/viewph > wZh){  //用紙より横に長い時は横いっぱいに拡大する
            prevsize.setSize(viewpw,viewpw/wZh);
            scale = viewpw/pwidth;
        }else{  //用紙より縦に長い時は縦いっぱいに拡大する
            prevsize.setSize(viewph*wZh,viewph);
            scale = viewph/pheight;
        }
        //prevsize.setSize(scale*595,scale*841);
        setPreferredSize(prevsize);
        //System.out.println(scale); //test
        g2.scale(scale,scale);
        drawPage(g2);
    }
Viewportの横/縦がA4のそれ(wZh)より大きい時はviewpwに合わせて拡大・縮小する Viewportの横/縦がA4のそれ(wZh)より小さい時はviewphに合わせて拡大・縮小する

viewportの幅に合わせるのと高さに合わせるのを比べて大きくなる方にするという解釈のほうがわかりやすいかもしれない。

振動現象

画像をクリックすると状況が再現されます

スクロールバーを出すとビューポートが小さくなってJPanelのサイズも小さくなるするとスクロールバーが不要になってビューポートが大きくなる。するとJPanelのサイズも大きくできるが、スクロールバーが必要になる... 縦横のスクロールバーの相互の影響でこのような振動が起きていると考えられる。もう少し緻密でないと正確ではないが...

改めてスクロールバーのあるなしでサイズを考える

用紙の縦横比(ここでは横÷縦を使う)の値をwZhという変数に入れてあります。今回はA4用紙なので210d/297dということになります。

ScrollPaneの窓部分をViewportと呼びます。この縦横比が用紙の縦横比(wZh)と比べてどうなっているかで条件分けします

条件1 Viewportの縦横比がwZhより十分に大きい場合

大きい時は拡大(縮小でも同じ)して横を一致させます。すると縦には入りきらないので、必ず縦方向のスクロールバーが必要になります。そこで横幅はViewportの横幅からスクロールバーの幅を引いたものになり、縦の高さは横幅を縦横比で割ったものになります。

Viewportの縦横比の方が大きい時といっても、それほどでもない時には、スクロールバーの幅を引いた幅に合わせると縦が入りきってしまう場合も考えられます。この場合はスクロールバーが必要なくなりますから計算が狂います。そこで、条件1は十分に大きい場合としましたが、正確には縦スクロールバーの幅を引いても縦横比がwZhより大きい場合ということになります。

右にスクロールバーがある想定でWを合わせる

条件2 Viewportの縦横比がwZhより十分に小さい場合

小さい時は拡大(縮小でも同じ)して縦を一致させます。すると横には入りきらないので、必ず横方向のスクロールバーが必要になります。そこで縦の高さはViewportの縦の高さからスクロールバーの高さを引いたものになり、横幅は縦の高さに縦横比をかけたものになります。

Viewportの縦横比の方が小さい時といっても、それほどでもない時には、スクロールバーの高さを引いた高さに合わせると横が入りきってしまう場合も考えられます。この場合はスクロールバーが必要なくなりますから計算が狂います。そこで、条件2は十分に小さい場合としましたが、正確には横スクロールバーの高さを引いても縦横比がwZhより小さい場合ということになります。

下にスクロールバーがある想定でhを合わせる

条件3 その他。Viewportの縦横比がwZhに近い場合

結論として、縦横比が大きい場合は縦を、小さい場合は横を合わせるように拡大(または縮小)します。

大きい場合は横を合わせていましたがスクロールバーが現れるとその分小さくなります。縦を合わせると横はスクロールバー程はない隙間が空きますが、全体としては横を合わせるより大きくなります。

縦横比が大きい場合は縦を合わせる

小さい場合は逆に縦を合わせていましたがスクロールバーが現れるとその分小さくなります。横を合わせると縦はスクロールバー程はない隙間が空きますが、全体としては縦を合わせるより大きくなります。

縦横比が小さい場合は横を合わせる

ちなみに、JViewportの報告するサイズはスクロールバーを除いた値です。

スクロールバーを考慮した3つめのバージョン

振動はなくなりました。

    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        int viewpw = getParent().getWidth();
        int viewph = getParent().getHeight();
        JScrollPane sp = (JScrollPane) getParent().getParent();
        int hbarh = sp.getHorizontalScrollBar().getHeight();
        int vbarw = sp.getVerticalScrollBar().getWidth();
        boolean hbarAri = sp.getHorizontalScrollBar().isVisible();  //注
        boolean vbarAri = sp.getVerticalScrollBar().isVisible();   //注
        if (vbarAri) viewpw += vbarw;  //注
        if (hbarAri) viewph += hbarh;  //注
        if((double)(viewpw-vbarw)/viewph > wZh){  //条件1
            prevsize.setSize(viewpw-vbarw, (viewpw-vbarw)/wZh);
            scale = (viewpw-vbarw)/pwidth;
        }else if( wZh > (double)viewpw / (viewph-hbarh)  ){  //条件2
            prevsize.setSize((viewph-hbarh)*wZh, viewph-hbarh);
            scale = (viewph-hbarh)/pheight;
        }else{  //条件3
            if ( (double)viewpw/viewph > wZh ) {
                prevsize.setSize(viewph*wZh, viewph);
                scale = viewph/pheight;
            }else{
                prevsize.setSize(viewpw, viewpw/wZh);
                scale = viewpw/pwidth;
            }
        }
        setPreferredSize(prevsize);
        //System.out.println(scale); //test
        g2.scale(scale,scale);
        drawPage(g2);
    }

説明の通り、条件1から条件3に分けて設定しています。

ただし、JViewportの報告するサイズはスクロールバーを除いた値です。考察ではスクロールバーの置かれるところも加えていましたので、それに合わせてスクロールバーがある場合にはその値を加えています。//注と書かれている部分です。

JScrollPaneにJpanelを継承したTsuuchiPrevクラスのインスタンス内で、getParent()はJViewportのことになります。さらにgetParent()を求めるとJScrollPaneになります。このようにして到達したScrollPaneからスクロールバーの状態や幅を取得しています。

実行結果 その3 スクロールも

開始の状態。すべて1/2にしてあります。クリックで大きくなります。

横に引き伸ばした状態。横幅に合わせて拡大されます

縦に引き伸ばした状態。高さに合わせて引き伸ばします。

ほぼ定比率で小さくした状態

現在のプログラム (TsuuchiPrev.java)

RenderingHintsについては説明していないが試せるようになっています。

swingのGUIということで、EDTからの呼び出し(invokeLaterを使う)という注意に従っていますが、この程度のプログラムでは必要性はないと思います。これについては後々書くかもしれません。

このプログラムの実行には次の3つのプログラムも必要です。これらはこれまでのページに全リストが掲載されていますが、今後も機能追加されるかも知れません。

AjustString.java
AjustStringT.java
Linemm.java        

TsuuchiPrev.java

package print01;

import java.awt.*;
import java.awt.print.*;
import java.awt.geom.Line2D;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.HashPrintJobAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.PrintJobAttributeSet;
import javax.print.attribute.standard.MediaSize;
import javax.print.attribute.standard.MediaSizeName;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.MediaPrintableArea;
import javax.print.attribute.standard.MediaTray;
import javax.print.attribute.Attribute;
import javax.swing.*;
import static java.awt.RenderingHints.*;

//SwingUtilities.invokeLaterを使う--2--
//paintComponent()の振動抑制(non ImageBuffer第一号 見やすく訂正) --3--

public class TsuuchiPrev extends JPanel implements Printable {
    public static final double a4longside  = 297d*72/25.4; // 841.88;
    public static final double a4shortside = 210d*72/25.4; // 595.27;
    public static final double pwidth = a4shortside;
    public static final double pheight = a4longside;
    public static final double wZh = pwidth/pheight;
    String[] kams;
    int[] tans, tens;
    Dimension prevsize;
    double scale;
    public TsuuchiPrev(String[] kams, int[] tans, int[] tens){
        this.kams = kams;
        this.tans = tans;
        this.tens = tens;
        setBackground(Color.white);
        prevsize = new Dimension(); //(int)pwidth,(int)pheight);
        prevsize.setSize(pwidth,pheight);
        setPreferredSize(prevsize);
    }
    @Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        int viewpw = getParent().getWidth();
        int viewph = getParent().getHeight();
        //int imgw = getWidth();
        //int imgh = getHeight();
        JScrollPane sp = (JScrollPane) getParent().getParent();
        int hbarh = sp.getHorizontalScrollBar().getHeight();
        int vbarw = sp.getVerticalScrollBar().getWidth();
        boolean hbarAri = sp.getHorizontalScrollBar().isVisible();
        boolean vbarAri = sp.getVerticalScrollBar().isVisible(); 
        if (vbarAri) viewpw += vbarw;
        if (hbarAri) viewph += hbarh;
        if((double)(viewpw-vbarw)/viewph > wZh){
            prevsize.setSize(viewpw-vbarw, (viewpw-vbarw)/wZh);
            scale = (viewpw-vbarw)/pwidth;
        }else if( wZh > (double)viewpw / (viewph-hbarh)  ){
            prevsize.setSize((viewph-hbarh)*wZh, viewph-hbarh);
            scale = (viewph-hbarh)/pheight;
        }else{
            if ( (double)viewpw/viewph > wZh ) {
                prevsize.setSize(viewph*wZh, viewph);
                scale = viewph/pheight;
            }else{
                prevsize.setSize(viewpw, viewpw/wZh);
                scale = viewpw/pwidth;
            }
        }
        //prevsize.setSize(scale*595,scale*841);
        setPreferredSize(prevsize);
        //System.out.println(scale); //test
        g2.scale(scale,scale);
        //g2.setRenderingHint(KEY_ANTIALIASING,VALUE_ANTIALIAS_ON);
        //g2.setRenderingHint(KEY_TEXT_ANTIALIASING,VALUE_TEXT_ANTIALIAS_ON);
        drawPage(g2);
    }
    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) {
        if (pageIndex != 0) return NO_SUCH_PAGE;
        Graphics2D g2 = (Graphics2D)g;
        drawPage(g2);
        return PAGE_EXISTS;
    }

    public void drawPage(Graphics2D g2){
        //if (pageIndex != 0) return NO_SUCH_PAGE;
        //g2.setRenderingHint(KEY_ANTIALIASING,VALUE_ANTIALIAS_ON);
        //g2.setRenderingHint(KEY_TEXT_ANTIALIASING,VALUE_TEXT_ANTIALIAS_GASP);
        float hbas = 20.0f;  //左端の位置
        float hwkm = 27.5f;  //科目名の幅
        float hwtn = 7f;     //単位数の幅
        float hptch = 45f/4;  //学期などの幅
        float vthtop = 41.01f; //ヘッダ行の上の位置
        float vtdtop = 64.01f; //行データの上の位置
        float vptch  = 9.8f;  //行データの各行の高さ
        float hwall = hwkm+hwtn+hptch*4;
        float mm2pt  = 72/25.4f;
        float pt2mm  = 25.4f/72;
        //Font font10 = new Font("Serif", Font.PLAIN, 10);
        Font font10 = new Font("IPA P明朝", Font.PLAIN, 10);
        BasicStroke boldstroke = new BasicStroke(1.0f);
        BasicStroke medmstroke = new BasicStroke(0.7f);
        BasicStroke thinstroke = new BasicStroke(0.0f);
        g2.setStroke(boldstroke);
        Linemm line = new Linemm();
        line.setTB(vthtop, vtdtop+vptch*kams.length);
        line.setX(hbas);
        g2.draw(line);
        line.setX(hbas+hwall);
        g2.draw(line);
        line.setLR(hbas, hbas+hwall);
        line.setY(vthtop);
        g2.draw(line);
        line.setY(vtdtop+vptch*kams.length);
        g2.draw(line);
        g2.setStroke(medmstroke);
        line.setY(vtdtop);
        g2.draw(line);

        g2.setStroke(thinstroke);
        line.setLR(hbas, hbas+hwall);
        for (int k=1; kams.length>k; k++){ //1 not 0
            line.setY(vtdtop+vptch*k);
            g2.draw(line);
        }
        line.setTB(vthtop, vtdtop+vptch*kams.length);
        line.setX(hbas+hwkm);
        g2.draw(line);
        for (int i=0; 4>i; i++){
            line.setX(hbas+hwkm+hwtn+hptch*i);
            g2.draw(line);
        }
        /*
        Linemm line2 = new Linemm();
        line2.setY(vtdtop+vptch*6 -0.001f);
        line2.setX(hbas, hbas+hwall-5);
        g2.draw(line2);
        */

        g2.setFont(font10);
        float padbtm = (vptch-g2.getFontMetrics().getHeight()*pt2mm)/2;
        float vm,hm;
        AjustString kp;
        kp = new AjustString(g2, "科目", hwkm-10f);
        kp.drawKintou(hbas+5f,vthtop+(vtdtop-vthtop)/2+g2.getFontMetrics().getHeight()*pt2mm/2);

        AjustStringT kt;
        kt = new AjustStringT(g2, "単位数" ,vtdtop-vthtop-4f);
        kt.drawTtoB(hbas+hwkm+hwtn/2,vthtop+2f);
        kt = new AjustStringT(g2, "一学期" ,vtdtop-vthtop-4f);
        kt.drawTtoB(hbas+hwkm+hwtn+hptch/2,vthtop+2f);
        kt = new AjustStringT(g2, "二学期" ,vtdtop-vthtop-4f);
        kt.drawTtoB(hbas+hwkm+hwtn+hptch*1.5f,vthtop+2f);
        kt = new AjustStringT(g2, "三学期" ,vtdtop-vthtop-4f);
        kt.drawTtoB(hbas+hwkm+hwtn+hptch*2.5f,vthtop+2f);
        kt = new AjustStringT(g2, "学年" ,vtdtop-vthtop-4f);
        kt.drawTtoB(hbas+hwkm+hwtn+hptch*3.5f,vthtop+2f);
        for (int k=0; kams.length>k; k++){
            vm = vtdtop+vptch*(k+1)-padbtm; //+1
            kp = new AjustString(g2, kams[k] ,hwkm-2f);
            
            if(kp.hasNext()){  //長い科目名を2行に
                
                kp.drawLeft(hbas+1f,vm+padbtm*3/4-vptch/2);
                kp = new AjustString(g2, kams[k] ,hwkm-2f, kp.getNextPt());
                kp.drawRight(hbas+1f,vm+padbtm/2);
                /* Fontを小さくする作戦
                g2.setFont(new Font("Serif", Font.PLAIN, 6));
                kp = new AjustString(g2, kams[k] ,hwkm-2f);
                kp.drawKintou(hbas+1f,vm);
                g2.setFont(font10);
                */
            }else{
            
                kp.drawKintou(hbas+1f,vm);
            }
            kp = new AjustString(g2, String.valueOf(tans[k]), hwtn);//単位数
            kp.drawCenter(hbas+hwkm,vm);
            kp = new AjustString(g2, String.format("%3d",tens[k]), hptch);//評点1
            hm = hbas+hwkm+hwtn;
            kp.drawCenter(hm,vm);
            /*
            if(gakki>=2){
                kp = new AjustString(g2, tenStringOf(kamokutens.ten23), hptch);//評点2
                kp.drawCenter(hm+hptch,vm);
            }
            if(gakki>=3){
                kp = new AjustString(g2, tenStringOf(kamokutens.ten33), hptch);//評点3
                kp.drawCenter(hm+hptch*2,vm);
                kp = new AjustString(g2, tenStringOf(kamokutens.ten5), hptch);//評点5
                kp.drawCenter(hm+hptch*3,vm);
            }
            */
        }
        //return PAGE_EXISTS;
    }

     public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
    public static void createAndShowGUI() {
        String[] kams = { "国語総合","現代社会","数学Ⅰ","数学A","化学基礎",
                              "生物基礎","体育","保健","音楽Ⅰ",//"コミュニケーⅠ",
                              "コミュニケーション英語Ⅰ",
                              "英語表現Ⅰ","家庭基礎","情報の科学" };
        int[] tans = { 6,3,4,2,2,
                           2,2,1,1,2,4,
                           2,2,2 };
        int[] tens = { 55,45,65,45,75,
                           45,55,55,60,45,
                           65,65,55 };

        PrinterJob pj = PrinterJob.getPrinterJob();
        PrintRequestAttributeSet reqset = new HashPrintRequestAttributeSet();
        MediaSizeName medname = MediaSizeName.ISO_A4;
        //MediaSizeName medname = MediaSizeName.JAPANESE_POSTCARD;
        reqset.add(medname);

        MediaSize medsize = MediaSize.getMediaSizeForName(medname);
        float medwidth  = medsize.getX(MediaPrintableArea.MM);
        float medheight = medsize.getY(MediaPrintableArea.MM);
        float topmm   = 8.8f;  //landscape前のTOP
        float bottomm = 7.7f;
        float leftmm  = 6.6f;
        float rightmm = 5.5f;
        reqset.add(new MediaPrintableArea(
           leftmm, topmm,
           (medwidth - leftmm - rightmm),
           (medheight - topmm - bottomm), MediaPrintableArea.MM));
        //reqset.add(OrientationRequested.REVERSE_LANDSCAPE);
        //reqset.add(OrientationRequested.LANDSCAPE);
        reqset.add(OrientationRequested.PORTRAIT);

        TsuuchiPrev prevPrintable = new TsuuchiPrev(kams,tans,tens);  //printable instance
        JScrollPane scrollPane = new JScrollPane(prevPrintable); // 
        String[] goOrNot = {"印刷","中止"};
        JOptionPane pane = new JOptionPane(
                                   scrollPane, 
                                   JOptionPane.PLAIN_MESSAGE, 
                                   JOptionPane.DEFAULT_OPTION,
                                   null,goOrNot,goOrNot[0]
                               );
        //JDialog dialog = pane.createDialog( null, "印刷プレビュー" );
        JDialog dialog = pane.createDialog("印刷プレビュー" );
        dialog.pack();
        dialog.setResizable(true);
        dialog.setVisible(true);
        String svalue = (String)pane.getValue();
        dialog.dispose(); //needs for ends /set HIDE_ON_CLOSE by default
        if (svalue==null || !svalue.equals("印刷")) System.exit(0);

        pj.setPrintable(prevPrintable);  //printable instance
        if (pj.printDialog( reqset )) {
            boolean debug = true;
            if(debug){
                Attribute[] attrs = reqset.toArray(); //test
                for(int n=0 ; attrs.length > n ; n++){ //test
                    System.out.println( attrs[n].getName()+":"+ attrs[n].toString()+";; ");
                }
            }
            try { pj.print( reqset ); }
            catch (PrinterException e) {
                System.err.println(e);
            }
        }
    }
}

備考

突然mainにSwingUtilities.invokeLaterを使いましたが、これについてはswingのスレッドポリシーをご覧ください。

最後のプログラム末尾近くにdialog.dispose();を追加しました(2017-04-28)。印刷後に System.exit(0);を加えるのでもOKですが、showOptionDialog()と異なり、カスタマイズされたJDialogを使うと正常なオプション選択でもクローズボタンでもHIDE_ON_CLOSEなので、プログラムが終了しなくなります。dialog.setDefaultCloseOperation( WindowConstants.DISPOSE_ON_CLOSE );を加えるのでは、正常なオプション選択をした時に終わらなくなります。詳しくは、 JDialogはHIDE_ON_CLOSEで残るに書きます。