SHOEISHA iD

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

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

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

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

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

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

(3)用紙サイズからプリンタを検索・選択するプログラムを作る

 よく考えてみると、javax.print.attribute.standard.MediaSizeNameに登録されている用紙であっても、プリンタ機種により印刷できないかもしれません。そこで、getSupportedAttributeValues()メソッドを使って指定サイズの用紙が使えるプリンタを検索し、得られたMediaSizeNameを使って用紙の指定と余白の指定をするプログラムを作りました。実際にやってみると考慮すべき点がいくつかあります。

 まず、大きさを比較するときは、長さがピッタリとは限らないので多少の違いがあっても合致したと判断する必要があります。いままでの調査でも長形3号の120mmを119.944mmと登録してある例がありました。そこで、例えば縦の差が2mm以内で、かつ横の差が2mm以内なら合致したとみなします。この2mmは任意に決めても構いませんが、JIS規格で定める誤差も2mm以内と決められています。

 誤差を認めると、複数の用紙が合致する可能性が大きくなります。最初に合致したものを採用してしまうと誤差の大きいものが採用されることもあり得るので、全部を調べてから差が最も小さいMediaSizeNameを採用するということが必要になります。しかし、実際にやってみると同じサイズが2つ以上の名前で登録されている場合があります。同じ名前て3つ登録されているプリンタもありました。名前にLANDSCAPEがついていると自動で印刷を90度回転といったような仕組みが隠されているかもしれません。どちらにせよ、一番近いものを自動で選択するのは危険です。手動で選択する必要が出てきます。

 さらにプリンタによりMediaSizeNameが異なる場合がありますから、プリンタとMediaSizeNameをセットで使用する必要があります。つまり、PX-1700Fというプリンタでは長形3号のMediaSizeName(の文字列表現)は「長形3号封筒 120 x 235 mm(101)」ですが、ダイアログでプリンタをMP493に変えたら、「長形3号(112)」のMediaSizeNameに替えなければならないということです。

 もう少し具体的に説明します。ServiceUI.printDialog()メソッドの引数を考えてください。プリンタの選択肢の配列と初期値のプリンタ、そしてPrintRequestAttributeSetのインスタンスです。プリンタ初期値にPX-1700F、PrintRequestAttributeSetに「長形3号封筒 120 x 235 mm(101)」とそれから計算したMediaPrintableAreaを入れてダイアログを呼び出したとします。ダイアログの「サイズ」の欄には「長形3号封筒 120 x 235 mm」余白にはMediaPrintableAreaの設定値から計算した値が表示されます。これはうまく行きます。

長形3号が選択されている
初期値のプリンタでは長形3号になっている

 問題はこの後です。ダイアログでプリンタをMP493に変えたとします。PrintRequestAttributeSetはもとのままで、MediaSizeNameはPX-1700Fから得られた「長形3号封筒 120 x 235 mm(101)」です。プリンタが変更されると、Javaはこの(101)を使って用紙の大きさを再取得してMediaPrintableAreaの設定値と合わせて余白の値を再計算しようとします。しかし、MP493には(101)の用紙がないので、用紙設定がデフォルトのA4になってしまいます。実際ダイアログでA4と表示されます。余白は変更されません。長形3号よりA4が大きいので余白を少なくする必要がないからです。MP493の長形3号は「長形3号(112)」ですから合わせて替える必要があるわけです。もし、MP493に(101)のサイズの用紙があった場合でも実際の用紙が同じとは限りません。余計に混乱します。また、余白の計算にも影響する場合があります。

A4になってしまう
プリンタを変更すると長形3号でなくA4になる

 この辺の事情を確認したい場合に備えて参考プログラムを出しておきます。プリンタを変更してページ設定のサイズがどうなるか確認できます。[印刷]を選択しても実際の印刷はせず、選択したプリンタに合わせてMediaSizeNameを設定し、端末に報告して終了します。

PrinterSupportedTest.java
import java.util.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
/** 
 指定したサイズ(x,y)の用紙を使用できるプリンタとMediaSizeNameを
 getSupportedAttributeValues()を使って調べ、
 ServiceUI.printDialog()を用いてプリンタをユーザーに選択させ、
 最終的に選択したプリンタ名とMediaSizeNameを表示する。
 印刷は一切しない。
 途中、端末上に次の内容がレポートされる。
 (1)用紙サイズに合致するMediaSizeNameを探す過程
 (2)プリンタごとの合致する用紙の一覧
 (3)UIでプリンタの変更をした後のMediaSizeNameの再設定の過程
 2019-8-21 adachi (from d02)
*/
public class PrinterSupportedTest {
    /**フィールド変数は余白と用紙の大きさを比較するときの許容誤差である。
    現在はただの定数だが、将来的に変更のためのメソッドを加えられるようにしている。
    メソッドがstaticでないのもこれが理由。*/
    private float L = 10.1f;
    private float R = 10.2f;
    private float T = 10.3f;
    private float B = 10.4f;
    private float diff = 2f;
    private float chotto = (float)Math.pow( 2, -24 );

    /** インスタンス化して、用紙の幅x、高さy(y>=x)、PrintRequestAttributeSetを引数に
    printerSetTo()を使ってサービスを得る。MediaSiazeNameなどはSetに加えられている。*/
    public static void main(String[] args) {
        int job = 4;
        float x=210f;
        float y=297f;
        switch (args.length) {
        case 2:
            x = Float.parseFloat(args[0]);
            y = Float.parseFloat(args[1]);
            break;
        case 1:
            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
            break;
        default:
            System.out.println("usage: java PrinterSupportedTest n");
            System.out.println(" 0:chou3 1:hagaki 2:kaku2 3:A3");
            System.out.println(" 4:A4    5:kaku6  6:you6 ");
            System.out.println("or java PrinterSupportedTest x y ...");
       }
        PrintRequestAttributeSet rqset = new HashPrintRequestAttributeSet();
        rqset.add(OrientationRequested.REVERSE_LANDSCAPE);
        PrinterSupportedTest psup = new PrinterSupportedTest();
        PrintService sv = psup.printerSetTo(x,y,rqset);
        System.out.println("--message from main ---------");
        System.out.println(">>set to :"+sv.getName());
        checkSet(rqset);
    }
    /** デバグ用 main()からのみ呼ばれる。*/
    public static void checkSet(PrintRequestAttributeSet set1){
        Media med1 = (Media)set1.get(Media.class);
        System.out.print(">>rqset :"+med1.toString());
        OrientationRequested ori1 =
            (OrientationRequested)set1.get(OrientationRequested.class);
        System.out.println(" :"+ori1.toString());
    }
    
    /** 用紙の幅x、高さy(y>=x)に、適合するプリンタを調べ
    ServiceUI.printDialog()で選択させて返す。
    余白をダイアログに出す必要上、初期値になるプリンタのMediaSiazeNameと
    MediaPrintableAreaをadd()したPrintRequestAttributeSetを引数にする。
    PrintRequestAttributeSetは明示的には返り値にしていないが、
    メソッド内の変更はメソッドから戻った後も有効になっている。
    これはServiceUI.printDialog()の振る舞いに合わせたものである。
    ダイアログでプリンタが変更されると、そのプリンタに合わせてMediaSiazeNameと
    MediaPrintableAreaをadd()し直す必要があるが、ユーザーがすでに変更
    している可能性や、使えるMediaSiazeNameが複数の場合も考慮して
    変更の必要を先にチェックしている。
    ダイアログ内で余白を変更することは考慮されていない。*/
    public PrintService printerSetTo(float x, float y, PrintRequestAttributeSet rqset){
        PrintService sv = null;
        float in2mm = 25.4f;
        Map<PrintService,List<MediaSizeName>> svmap = getMap(x,y);
        System.out.println("-- map summary in printerSetTo method ---------");
        for (Map.Entry<PrintService,List<MediaSizeName>> entry : svmap.entrySet()) {
            System.out.print(entry.getKey().getName() +"\t" );
            List<MediaSizeName> msnlist = entry.getValue();
            for(MediaSizeName msn :msnlist){
                System.out.print(
                    ":"+msn.toString() +"("+msn.getClass()+"|"+msn.getValue()+") ");
            }
            System.out.println();
        }
        if(svmap.size()>0){
            //map to array
            PrintService[] ssvs = svmap.keySet().toArray(new PrintService[0]);
            List<MediaSizeName> msnlist = svmap.get(ssvs[0]);
            MediaSizeName msn = msnlist.get(0);
            rqset.add(msn);
            MediaSize msize = MediaSize.getMediaSizeForName(msn);
            float mwidth  = msize.getX(MediaPrintableArea.MM);
            float mheight = msize.getY(MediaPrintableArea.MM);
            //Areaはmsnから計算するから場合によってはここでは早すぎる
            MediaPrintableArea mparea = new MediaPrintableArea(
                L, T, (mwidth - L - R), (mheight - T - B),
                MediaPrintableArea.MM);
            rqset.add(mparea);
            sv = ServiceUI.printDialog(
                null, 200, 200, ssvs, ssvs[0], null, rqset);
            if (sv != null) {
                System.out.print(">>selected :"+sv.getName());
                Media med = (Media)rqset.get(Media.class);
                System.out.println(" :"+med.toString());
                MediaSizeName msnx = null;
                if(med instanceof MediaSizeName){
                    msnx = (MediaSizeName)med;
                    if(!svmap.get(sv).contains(msnx)){
                        msn = svmap.get(sv).get(0);
                        rqset.add(msn);
                        rqset.add(mparea);
                        System.out.println(
                            "replace to " + msn.toString() + " and set mparea again");
                    }
                }
                med = (Media)rqset.get(Media.class);
                System.out.println("last media = "+med.toString());
                System.out.println(
                    "Supported "+ sv.isAttributeValueSupported(med, null, rqset));
            }
        }
        return sv;
    }
    /** (x,y)の用紙を扱えるサービスをkeyとし、MediaSizeNameのリストを値とするマップ を返す
    lookupPrintServices()とDefaultService()とgetMached()を使って作成する。
    printerSetTo()から呼ばれるメソッド */
    public Map<PrintService,List<MediaSizeName>> getMap(float x, float y){
        PrintService[] svs = PrintServiceLookup.lookupPrintServices(null,null);
        Map<PrintService,List<MediaSizeName>> svmap = new LinkedHashMap<>();
        PrintService dsv = PrintServiceLookup.lookupDefaultPrintService();
        List<MediaSizeName> msnlist = getMatched(dsv, x, y);
        if(!msnlist.isEmpty()) svmap.put(dsv,msnlist);
        for (PrintService sv : svs) {
            if(sv.equals(dsv)){
                System.out.println(sv.getName()+" is same to "+dsv.getName());
            }
            msnlist = getMatched(sv, x, y);
            if(!msnlist.isEmpty()) svmap.put(sv,msnlist);
        }
        return svmap;
    }

    /** 与えられたサービスがサポートする(x,y)の用紙に対応するMediaSizeNameのリストを返す 
    getSupportedAttributeValues()を使う。内部ではx,yの差の和をkeyとしMediaSizeNameを値とする
    マップをTreeMapで作成しておき、リストに変更して返す。より近いものが先に登録されている。
    なければ、空のリストのはず。
    getMap()から呼ばれるメソッド*/
    public List<MediaSizeName> getMatched(PrintService sv, float x, float y){
        Media[] meds = (Media[])sv.getSupportedAttributeValues(Media.class, null, null);
        SortedMap<Float,MediaSizeName> map = new TreeMap<>();
        int ct = 0;
        for (Media med : meds) {
            if(med instanceof MediaSizeName){
                MediaSizeName msn = (MediaSizeName)med;
                MediaSize ms = MediaSize.getMediaSizeForName(msn);
                if(ms!=null){
                    float msx = ms.getX(MediaSize.MM);
                    float msy = ms.getY(MediaSize.MM);
                    if(diff>=Math.abs(msx-x) && diff>=Math.abs(msy-y)){
                        System.out.println(msx+" "+msy+"\t:"
                            +sv.getName()+"\t:"
                            +msn.toString()+"\t:"
                            +msn.getValue()+"\t:"
                            +Math.abs(msx-x)+"+"+Math.abs(msy-y)+"+"+chotto*ct
                            +"="+(Math.abs(msx-x) + Math.abs(msy-y) + chotto*ct));
                        map.put(Math.abs(msx-x) + Math.abs(msy-y) + chotto*ct,msn);
                        ct++;
                    }
                 }
            }
        }
        return new ArrayList<MediaSizeName>(map.values());
    }
}

 TreeMapのための変数chottoについては次の「PrinterSupported.java」の説明をご覧ください。

実行時の端末表示(Windows)
PS Z:\java\print01> java PrinterSupportedTest 0
120.0 235.0     :EPSON PX-1700F :長形3号封筒 120 x 235 mm       :6      :0.0+0.0+0.0=0.0
120.0 235.0     :Microsoft XPS Document Writer  :封筒 長形 3 号 :37     :0.0+0.0+0.0=0.0
120.0 235.0     :Fax    :封筒 長形 3 号 :37     :0.0+0.0+0.0=0.0
EPSON PX-1700F is same to EPSON PX-1700F
120.0 235.0     :EPSON PX-1700F :長形3号封筒 120 x 235 mm       :6      :0.0+0.0+0.0=0.0
120.0 235.0     :Canon MP493 series Printer     :長形3号        :118    :0.0+0.0+0.0=0.0
120.0 235.0     :Canon Inkjet iP9910    :長形3号        :118    :0.0+0.0+0.0=0.0
-- map summary in printerSetTo method ---------
EPSON PX-1700F  :長形3号封筒 120 x 235 mm(6)
Microsoft XPS Document Writer   :封筒 長形 3 号(37)
Fax     :封筒 長形 3 号(37)
Canon MP493 series Printer      :長形3号(118)
Canon Inkjet iP9910     :長形3号(118)
>>selected :Canon MP493 series Printer :iso-a4   #iso-a4になってしまったので、
replace to 長形3号 and set mparea again          #MP493に合わせて長形3号にセットし直す
last media = 長形3号
Supported true
--message from main ---------
>>set to :Canon MP493 series Printer
>>rqset :長形3号 :reverse-landscape

 #の後は追加した説明です

 ServiceUI.printDialog()メソッドでは不満が残ります。プリンタとMediaSizeNameをセットで替えるようにするには独自のダイアログを使うほうが落ち着きます。

使えるプリンタ選択のプログラム

 前項を踏まえて、使えるプリンタを独自のダイアログで選択するプログラムを作ります。用紙の横縦のサイズとPrintRequestAttributeSetを引数にして呼び出すと、この用紙を使えるプリンタを探し、複数あるときはダイアログで選択させ、 PrintRequestAttributeSetにはMediaSizeNameをセットし、選択したプリンタを返します。

 mainにはテストとして、起動時の引数で、"なし:A4 0:chou3 1:hagaki 3:A3"を切り替えて動作確認ができるようになっています。プリンタや用紙名が複数になるときは独自のダイアログで選択します。印刷はせず、最終的なプリンタ名と用紙名を表示します。

 mainでは呼び出す前に*DUMPの値をtrueにして、端末上に途中経過をレポートします。

PrinterSupported.java
import java.util.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
import javax.swing.JOptionPane;
import static javax.swing.JOptionPane.*;
/**
 PrinterSupported.java
 用紙の横縦のサイズとPrintRequestAttributeSetを引数に
 この用紙を使えるプリンタを探し、複数あるときはダイアログで選択させ、
 選択したプリンタを返す。
 PrintRequestAttributeSetには、次のものをaddする。
 1.MediaSiazeName
 2.MediaPrintableArea (余白の値はフィールド変数を使用)
 これは陽には戻値としない
 2019-06-02 adachi
*/
public class PrinterSupported {
    /**フィールド変数は余白と用紙の大きさを比較するときの許容誤差である。
    現在はただの定数だが、将来的に変更のためのメソッドを加えられるようにしている。
    chottoはMapのkeyに同じFloat値を入れるための加算値。
    本来の大小関係を崩さない程度の値。*/
    private float L = 10.1f;
    private float R = 10.2f;
    private float T = 10.3f;
    private float B = 10.4f;
    private float diff = 2f;
    private float chotto = (float)Math.pow( 2, -24 );
    public boolean MAPDUMP = false;
    public boolean GETMAPDUMP = false;
    public boolean RESULTDUMP = false;
    /** インスタンス化して、用紙の幅x、高さy(y>=x)、PrintRequestAttributeSetを引数に
     printerSetTo()を使ってサービスを得る。MediaSiazeNameなどはSetに加えられている。*/
    public static void main(String[] args) {
        int job = 4;
        float x=210f;
        float y=297f;
        switch (args.length) {
        case 2:
            x = Float.parseFloat(args[0]);
            y = Float.parseFloat(args[1]);
            break;
        case 1:
            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
            break;
        default:
            System.out.println("usage: java PrinterSupported05 n");
            System.out.println(" 0:chou3 1:hagaki 2:kaku2 3:A3");
            System.out.println(" 4:A4    5:kaku6  6:you6 ");
            System.out.println("or java PrinterSupported05 x y ...");
       }
        PrintRequestAttributeSet rqset = new HashPrintRequestAttributeSet();
        //rqset.add(OrientationRequested.REVERSE_LANDSCAPE);
        PrinterSupported supp = new PrinterSupported();
        supp.MAPDUMP = true;
        supp.GETMAPDUMP = true;
        supp.RESULTDUMP = true;
        PrintService sv = supp.printerSetTo(x,y,rqset);
        if (sv!=null) {
            System.out.println(">>set to :"+sv.getName());
            checkSet(rqset);
        }else{
            System.out.println(">>canceled");
        }
    }
    /** デバグ用 main()からのみ呼ばれる。*/
    public static void checkSet(PrintRequestAttributeSet set1){
        Media med1 = (Media)set1.get(Media.class);
        System.out.print(" :"+med1.toString());
        OrientationRequested ori1 =
             (OrientationRequested)set1.get(OrientationRequested.class);
        System.out.println((ori1!=null)?" :"+ori1.toString():" :portrait");
    }
    
    /** 用紙の幅x、高さy(y>=x)に、適合するプリンタを調べ独自なダイアログで選択させて返す。
    showInputDialog()でプリンタを選択し、必要ならば次にMediaSiazeNameを選択し、
    MediaPrintableAreaも合わせてPrintRequestAttributeSetにadd()する。
    PrintRequestAttributeSet は明示的には返り値にしていないが、メソッド内の変更はメソッドから
    戻った後に有効になっている。これはServiceUI.printDialog()の振る舞いに合わせたものである。*/
    public PrintService printerSetTo(float x, float y, PrintRequestAttributeSet rqset){
        PrintService sv = null;
        float in2mm = 25.4f;
        Map<PrintService,List<MediaSizeName>> svmap = getMap(x,y);
        if(MAPDUMP){ 
            for (Map.Entry<PrintService,List<MediaSizeName>> entry : svmap.entrySet()) {
                System.out.print(entry.getKey().getName() +"\t:" );
                List<MediaSizeName> msnlist = entry.getValue();
                for(MediaSizeName msn :msnlist){
                    System.out.print(msn.toString() +"("+msn.getValue()+")");
                }
                System.out.println("...["+msnlist.size()+"]");
            }
        }
        if(svmap.size()>0){
            PrintService[] ssvs = svmap.keySet().toArray(new PrintService[0]);
            sv = (PrintService)showInputDialog(
                    null, //parent,
                    "使用するプリンタを選んでください",//message,
                    "プリンタの選択",//title,
                    JOptionPane.QUESTION_MESSAGE, //msgTp,
                    null, //icon,
                    ssvs, ssvs[0]
                  );
            if (sv == null) return sv;
            MediaSizeName[] msns = svmap.get(sv).toArray(new MediaSizeName[0]);
            //List<MediaSizeName> msnlist = svmap.get(sv);
            MediaSizeName msn = null;
            if(msns.length>1){
                msn = (MediaSizeName)showInputDialog(
                    null, //parent,
                    "使用する用紙を選んでください",//message,
                    "用紙の選択",//title,
                    JOptionPane.INFORMATION_MESSAGE, //msgTp,
                    null, //icon,
                    msns, msns[0]
                      );
            }
            if(msn==null) {
                msn=msns[0];
                if(RESULTDUMP){
                  System.out.println("Only one or canceled so set to :"+msn.toString());
                }
            }
            rqset.add(msn);
            MediaSize msize = MediaSize.getMediaSizeForName(msn);
            float mwidth  = msize.getX(MediaPrintableArea.MM);
            float mheight = msize.getY(MediaPrintableArea.MM);
            MediaPrintableArea mparea = new MediaPrintableArea(
                L, T, (mwidth - L - R), (mheight - T - B),
                MediaPrintableArea.MM);
            rqset.add(mparea);
            if(RESULTDUMP){
                System.out.print(">>selected :"+sv.getName());
                Media med = (Media)rqset.get(Media.class);
                System.out.println(" :"+med.toString());
                System.out.println(
                    "Supported "+ sv.isAttributeValueSupported(med, null, rqset));
            }
        }else{
            showMessageDialog(
                    null,//parent,
                    x+" × "+y+" の用紙を印刷できるプリンタがありません",//message,
                    "残念な報告",//title
                    JOptionPane.ERROR_MESSAGE //msgTp
                  );
            if(RESULTDUMP){
                System.out.print("no printer for : "+x+" × "+y);
            }

        }
        return sv;
    }
    /** (x,y)の用紙を扱えるサービスをkeyとし、MediaSizeNameのリストを値とするマップ を返す
    lookupPrintServices()とDefaultService()とgetMached()を使って作成する。
    printerSetTo()から呼ばれるメソッド */
    public Map<PrintService,List<MediaSizeName>> getMap(float x, float y){
        PrintService[] svs = PrintServiceLookup.lookupPrintServices(null,null);
        if (GETMAPDUMP) System.out.println("lookup-count: "+svs.length);
        Map<PrintService,List<MediaSizeName>> svmap = new LinkedHashMap<>();
        PrintService dsv = PrintServiceLookup.lookupDefaultPrintService();
        List<MediaSizeName> msnlist = getMatched(dsv, x, y);
        if(!msnlist.isEmpty()) svmap.put(dsv,msnlist);
        for (PrintService sv : svs) {
            if(sv.equals(dsv)) continue;
            msnlist = getMatched(sv, x, y);
            if(!msnlist.isEmpty()) svmap.put(sv,msnlist);
        }
        return svmap;
    }

    /** 与えられたサービスがサポートする(x,y)の用紙に対応するMediaSizeNameのリストを返す 
    getSupportedAttributeValues()を使う。内部ではx,yの差の和をkeyとしMediaSizeNameを値とする
    マップをTreeMapで作成しておき、リストに変更して返す。より近いものが先に登録されている。
    なければ、空のリストのはず。
    getMap()から呼ばれるメソッド*/
    public List<MediaSizeName> getMatched(PrintService sv, float x, float y){
        Media[] meds = (Media[])sv.getSupportedAttributeValues(Media.class, null, null);
        if (GETMAPDUMP) System.out.println(sv.getName()+" media-count: "+meds.length);
        SortedMap<Float,MediaSizeName> map = new TreeMap<>();
        int ct = 0;
        for (Media med : meds) {
            if(med instanceof MediaSizeName){
                MediaSizeName msn = (MediaSizeName)med;
                MediaSize ms = MediaSize.getMediaSizeForName(msn);
                if(ms!=null){
                    float msx = ms.getX(MediaSize.MM);
                    float msy = ms.getY(MediaSize.MM);
                    if(diff>=Math.abs(msx-x) && diff>=Math.abs(msy-y)){
                        if (GETMAPDUMP){
                            System.out.println(msx+" "+msy+"\t:"
                            +msn.toString()+"\t:"
                            +msn.getValue()+"\t:"
                            +Math.abs(msx-x)+"+"+Math.abs(msy-y)+"+"+chotto*ct
                            +"="+(Math.abs(msx-x) + Math.abs(msy-y) + chotto*ct));
                        }
                        map.put(Math.abs(msx-x) + Math.abs(msy-y) + chotto*ct,msn);
                        ct++;
                    }
                }
            }
        }
        return new ArrayList<MediaSizeName>(map.values());
    }
}

デフォルトプリンタを最初に

 getMap()でlookupPrintServices()を使って得られたプリンタを調べる前に、lookupDefaultPrintService()で得られるデフォルトプリンタを先に評価して、Mapに加えています。これにより、デフォルトプリンタが使えるなら、最初の候補になります。

TreeMapとchottoについて

 getMatched()メソッドでは、与えられたサービスから、x,y の値に合致したものを取り出したいのですが、(120,135)のはずが登録されているのが(119.9,234.9)だったという場合もあります。近いものが見つかれば用が足りるので差が2mm以下のものを集めています。もちろん浮動小数点数を==で比較すべきではないという原則にもあっています。

 複数の候補がある場合は、より近いものを上位にしたいので、差をkeyにし、MediaSizeNameをvalueにしてTreeMapに追加します。ところがやっているうちに、同じサイズで別名で登録されているものがあることに気づきました。「xxx」と「xxx landscape」みたいなやつです。放置すると「xxx landscape」の方だけが残ったりします。そこで、chottoの導入です。keyにしている差の順位に影響がない程度の値を足して、差が同じでも出現順位をキープしつつ複数登録するようにとの工夫です。

 メソッドの終わりにこのMapからvalueだけのListを作って返しますので、keyそのものは残りません。

余白の値と一致判断のための差の変更について

 フィールド変数L,R,T,Bは余白、diffは用紙の大きさを比較するときの許容誤差です。このプログラムでは変更不可な定数です。プリンタに合わせて、直接書き換えることで問題はないと思い、あえて外してあります。必要ならば変更のためのメソッドを追加することをお勧めします。その推奨のためのprivateです。

独自のダイアログについて

 このプログラムはプリンタや用紙名が複数になるときに独自のダイアログを出して選択させます。プリンタ選択は次のようになっています。

PrinterSupported.javaで使用しているプリンタ選択ダイアログ
sv = (PrintService)showInputDialog(
     null, //parent,
      "使用するプリンタを選んでください",//message,
      "プリンタの選択",//title,
     JOptionPane.QUESTION_MESSAGE, //msgTp,
     null, //icon,
     ssvs, ssvs[0]
);

 これはjavax.swing.JOptionPaneのstaticメソッドであるshowInputDialog()のうちの一つをそのまま利用したものです。

 このメソッドは選択候補の配列(上ではssvs)と初期値(上ではssvs[0])を指定して選択してもらい、その値を返します。オブジェクトの配列の中から直接選択できるところが面白い仕組みです。普通は文字列の配列を用意して、文字列を得たり、画像の配列を用意して画像を得たりしますが、今回はPrintServiceの配列を用意して、PrintServiceを得ます。

 便利な方法ですが、この場合の問題は画面表示です。オブジェクトのtoString()の値を使うことになっているため、文字列として長くなってしまっています。WindowsではWin32 Printer : EPSON PX-1700F、Win32 Printer : Canon MP493 series Printer など「|Win32 Printer :」という文字列が頭に付きます。Linuxでは「IPP Printer :」が付いています。getName()メソッドを使えばこういうことはないのですが、文字列の配列を作って選択してもらい、そこからPrinterServiceに戻すのは面倒なのでこのままにしています。

 showInputDialog()メソッドは、次のように説明されています。

showInputDialog()メソッド
static Object
showInputDialog(
    Component parentComponent,
    Object message,
    String title,
    int messageType,
    Icon icon,
    Object[] selectionValues,
    Object initialSelectionValue
)

使えるプリンタ選択の実行結果

実行結果(Windows 10)

 気になる長形3号の場合です。実行時引数を0にしています。

 まず最初にgetMatched()内の出力です。プリンタ名と行数の後、120.0 235.0で始まる部分は見つかった時に出しているものです。MadiaSizeNameの文字列表現がプリンタにより異なるのが分かります。その値(:3 :41 :113)も異なります。この値はMediaSizeからサイズを出すためのindexになっているようです。0.0+...はTreeMapのkeyの値です。

実行時の端末表示(Windows)
PS Z:\java\print01> java PrinterSupported 0
lookup-count: 8
Canon MP493 series Printer media-count: 40
120.0 235.0     :長形3号        :3      :0.0+0.0+0.0=0.0
OneNote media-count: 3
Microsoft XPS Document Writer media-count: 96
120.0 235.0     :封筒 長形 3 号 :41     :0.0+0.0+0.0=0.0
Microsoft Print to PDF media-count: 10
Fax media-count: 85
120.0 235.0     :封筒 長形 3 号 :41     :0.0+0.0+0.0=0.0
EPSON5AE8BC (PX-1700F) media-count: 38
120.0 235.0     :長形3号封筒 120 x 235 mm       :113    :0.0+0.0+0.0=0.0
EPSON PX-1700F media-count: 38
120.0 235.0     :長形3号封筒 120 x 235 mm       :113    :0.0+0.0+0.0=0.0
Canon Inkjet iP9910 media-count: 30
120.0 235.0     :長形3号        :3      :0.0+0.0+0.0=0.0

 プリンタ名で始まる行はmainからの出力です。プリンタ名のあとMediaSizeNameのリストですが[1]はリストの長さが1であることを指します。

実行時の端末表示(Windows)
Canon MP493 series Printer      :長形3号(3)...[1]
Microsoft XPS Document Writer   :封筒 長形 3 号(41)...[1]
Fax     :封筒 長形 3 号(41)...[1]
EPSON5AE8BC (PX-1700F)  :長形3号封筒 120 x 235 mm(113)...[1]
EPSON PX-1700F  :長形3号封筒 120 x 235 mm(113)...[1]
Canon Inkjet iP9910     :長形3号(3)...[1]

 この後、このプリンタを選択肢にダイアログを出します。showInputDialog()を使っています。

プリンタの選択
プリンタの選択(Windows 10)

 MediaSizeNameのリストに複数登録されていればダイアログがでます。長形3号では1つなので、このダイアログは出ません。

用紙の選択
用紙の選択(Windows 10)

 最終的な選択結果を表示します。

実行時の端末表示(Windows)
Only one or canceled so set to :長形3号
>>selected :Canon MP493 series Printer :長形3号
Supported true
>>set to :Canon MP493 series Printer
 :長形3号 :portrait

 プリンタは「Canon MP493 series Printer」、サイズは「長形3号」、縦置きであることを報告しています。念のためにisAttributeValueSupported()で確認してTrueを表示しています。

 ダイアログでプリンタを変更した場合には、それに合わせたMediaSizeNameを登録し直します。

実行時の端末表示(Windows)
Only one or canceled so set to :長形3号封筒 120 x 235 mm
>>selected :EPSON PX-1700F :長形3号封筒 120 x 235 mm
Supported true
>>set to :EPSON PX-1700F
 :長形3号封筒 120 x 235 mm :portrait

指定の用紙を扱えるプリンタがなかったときはこうなります。

実行時の端末表示(Windows)
PS Z:\java\print01> java PrinterSupported 238 312
lookup-count: 10
EPSON PX-1700F media-count: 38
OneNote media-count: 3
Microsoft XPS Document Writer media-count: 96
Microsoft Print to PDF media-count: 10
Fax media-count: 85
EPSON5AE8BC (PX-1700F) media-count: 38
Canon MP493 series Printer media-count: 40
Canon Inkjet iP9910 media-count: 30
no printer for : 238.0 × 312.0>>canceled
no printer
指定用紙が使用できるプリンタがない時(Windows)

実行結果(Linux)

 Linuxの場合は、用紙の名前など異なります。

実行時の端末表示(Linux)
adachi@debian64:~$ java PrinterSupported 0
lookup-count: 2
Canon_MP493_series media-count: 146
119.944 234.95	:Japanese long envelope #3	:83	:0.055999756+0.05000305+0.0=0.10600281
EPSON_EPSON_PX-1700F media-count: 30
120.015 234.985	:Envelope 120 x 235 mm	:887	:0.01499939+0.01499939+0.0=0.02999878
Canon_MP493_series	:Japanese long envelope #3(83)...[1]
EPSON_EPSON_PX-1700F	:Envelope 120 x 235 mm(887)...[1]
Only one or canceled so set to :Japanese long envelope #3
>>selected :Canon_MP493_A4 :Japanese long envelope #3
Supported true
>>set to :Canon_MP493_A4
 :Japanese long envelope #3 :portrait
プリンタの選択
プリンタの選択(Linux)

次のページ
PrinterJobでプリンタ選択印刷

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

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

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

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

イベント

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

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

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

メールバックナンバー