OSはDebian9(Strech)64bit、CUPS 2.2.1、プリンタはCanon MP493、javac 1.8.0_181 で用紙の選択がうまくできない問題。数年前から取り組んでいるのでOSやソフトウェアのバージョン、プリンタ機種によらず同様だと言える。Windowsでは同様の問題があるかどうかすら調べていない。
PrintRequestAttributeSetで用紙の余白や向きを指定して印刷するのは可能。
printDialog(pset)で指定した向きと余白がダイアログに表示される。予め指定したAttributeをここで変更して印刷に反映させることができる。
ところが用紙の指定、つまりA4とか葉書とか長3封筒とかの指定が無視される。予め指定したものがダイアログに出てくるがそのとおりにならない。
ブラウザで設定する、CUPSのDefaultOptionsで指定した用紙が使われる。
調査過程をそのまま書いているので冗長になっている。結論を先に書いておく。解決としては完全な理解ではないので参照が必要な場合も考えられる。
CUPSで同じプリンタをDefaultを変えて複数別名で登録し、javaからサービスを名前から選択して印刷することで解決。
サービスを検索する方法は大雑把に2つあるが、まだ違いが見えない。この方法のためにはどちらでも良い。
Java8なのはDebian9のリポジトリでのバージョンが8だから。ただしMediaSizeNameあたりの様子はJava10でもAPI仕様を見る限り変化はない。
MediaSizeNameやMediaSizeの調査。
必要があったら見てください。下で結果をまとめています
PrintRequestAttributeSet prset = new HashPrintRequestAttributeSet();
//MediaSizeName msname = MediaSizeName.JIS_B5;
//MediaSizeName msname = MediaSizeName.ISO_A4;
MediaSizeName msnameP = MediaSizeName.JAPANESE_POSTCARD;
MediaSize msfnmpost = MediaSize.getMediaSizeForName(msnameP);
System.out.println("(1)MediaSizeName.JAPANESE_POSTCARD");
System.out.println("getMediaSizeName()="+msfnmpost.getMediaSizeName());
//System.out.println("getName()="+msfnmpost.getName());
System.out.println("toString()="+msfnmpost.toString());
System.out.println("getX()="+msfnmpost.getX(MediaPrintableArea.MM));
System.out.println("getY()="+msfnmpost.getY(MediaPrintableArea.MM));
MediaSizeName msnameA4 = MediaSizeName.ISO_A4;
MediaSize msfnmA4 = MediaSize.getMediaSizeForName(msnameA4);
System.out.println("(2)MediaSizeName.ISO_A4");
System.out.println("getMediaSizeName()="+msfnmA4.getMediaSizeName());
//System.out.println("getName()="+msfnmA4.getName());
System.out.println(msfnmA4);
System.out.println("getX()="+msfnmA4.getX(MediaPrintableArea.MM));
System.out.println("getY()="+msfnmA4.getY(MediaPrintableArea.MM));
MediaSize msothp = MediaSize.Other.JAPANESE_POSTCARD;
System.out.println("(3)MediaSize.Other.JAPANESE_POSTCARD");
System.out.println("getMediaSizeName()="+msothp.getMediaSizeName());
//System.out.println("getName()="+msothp.getName());
System.out.println("toString()="+msothp.toString());
System.out.println("getX()="+msothp.getX(MediaPrintableArea.MM));
System.out.println("getY()="+msothp.getY(MediaPrintableArea.MM));
MediaSize mscho3 = MediaSize.JIS.CHOU_3;
System.out.println("(4)MediaSize.JIS.CHOU_3");
System.out.println("getMediaSizeName()="+mscho3.getMediaSizeName());
//System.out.println("getName()="+mscho3.getName());
System.out.println("toString()="+mscho3.toString());
System.out.println("getX()="+mscho3.getX(MediaPrintableArea.MM));
System.out.println("getY()="+mscho3.getY(MediaPrintableArea.MM));
MediaSize msisoa4 = MediaSize.ISO.A4;
System.out.println("(5)MediaSize.ISO.A4");
System.out.println("getMediaSizeName()="+msisoa4.getMediaSizeName());
//System.out.println("getName()="+msisoa4.getName());
System.out.println("toString()="+msisoa4.toString());
System.out.println("getX()="+msisoa4.getX(MediaPrintableArea.MM));
System.out.println("getY()="+msisoa4.getY(MediaPrintableArea.MM));
MediaSize msmyhaga = new MediaSize(100.0f,148.0f,MediaPrintableArea.MM);
System.out.println("(6)new MediaSize(100.0f,148.0f,MediaPrintableArea.MM)");
System.out.println("getMediaSizeName()="+msmyhaga.getMediaSizeName());
//System.out.println("getName()="+msmyhaga.getName());
System.out.println("toString()="+msmyhaga.toString());
System.out.println("getX()="+msmyhaga.getX(MediaPrintableArea.MM));
System.out.println("getY()="+msmyhaga.getY(MediaPrintableArea.MM));
MediaSize msmycho3 = new MediaSize(120.0f,235.0f,MediaPrintableArea.MM);
System.out.println("(7)new MediaSize(120.0f,235.0f,MediaPrintableArea.MM)");
System.out.println("getMediaSizeName()="+msmycho3.getMediaSizeName());
//System.out.println("getName()="+msmycho3.getName());
System.out.println("toString()="+msmycho3.toString());
System.out.println("getX()="+msmycho3.getX(MediaPrintableArea.MM));
System.out.println("getY()="+msmycho3.getY(MediaPrintableArea.MM));
MediaSize msmya4 = new MediaSize(210.0f,297.0f,MediaPrintableArea.MM);
System.out.println("(8)new MediaSize(210.0f,297.0f,MediaPrintableArea.MM)");
System.out.println("getMediaSizeName()="+msmya4.getMediaSizeName());
//System.out.println("getName()="+msmya4.getName());
System.out.println("toString()="+msmya4.toString());
System.out.println("getX()="+msmya4.getX(MediaPrintableArea.MM));
System.out.println("getY()="+msmya4.getY(MediaPrintableArea.MM));
MediaSizeName mssname = MediaSize.findMedia(120.0f,235.0f,MediaPrintableArea.MM);
System.out.println("(11)"+mssname);
mssname = MediaSize.findMedia(100.0f,148.0f,MediaPrintableArea.MM);
System.out.println("(12)"+mssname);
mssname = MediaSize.findMedia(210.0f,297.0f,MediaPrintableArea.MM);
System.out.println("(13)"+mssname);
}
実行結果
(1)MediaSizeName.JAPANESE_POSTCARD getMediaSizeName()=japanese-postcard toString()=100000x148000 um getX()=100.0 getY()=148.0 (2)MediaSizeName.ISO_A4 getMediaSizeName()=iso-a4 210000x297000 um getX()=210.0 getY()=297.0 (3)MediaSize.Other.JAPANESE_POSTCARD getMediaSizeName()=japanese-postcard toString()=100000x148000 um getX()=100.0 getY()=148.0 (4)MediaSize.JIS.CHOU_3 getMediaSizeName()=null toString()=120000x235000 um getX()=120.0 getY()=235.0 (5)MediaSize.ISO.A4 getMediaSizeName()=iso-a4 toString()=210000x297000 um getX()=210.0 getY()=297.0 (6)new MediaSize(100.0f,148.0f,MediaPrintableArea.MM) getMediaSizeName()=null toString()=100000x148000 um getX()=100.0 getY()=148.0 (7)new MediaSize(120.0f,235.0f,MediaPrintableArea.MM) getMediaSizeName()=null toString()=120000x235000 um getX()=120.0 getY()=235.0 (8)new MediaSize(210.0f,297.0f,MediaPrintableArea.MM) getMediaSizeName()=null toString()=210000x297000 um getX()=210.0 getY()=297.0 (11)null (12)japanese-postcard (13)iso-a4
A4の時にいろいろ調べて、MediaSizeNameから設定をスタートしていた。
今回使う可能性のあるのは、葉書、長3封筒、A4。
MediaSizeNameが定義されているのは、MediaSizeName.JAPANESE_POSTCARD, MediaSizeName.ISO_A4 の2つ。長3はない。
MediaSizeNameがあれば、MediaSizeから、X,Yを得られる
MediaSize MediaSize.getMediaSizeForName(MediaSizeName media) float MediaSize#getX(int units) MediaSizeName⇨MediaSize⇨X,Y
MediaSizeは文字列でも数値でもないので、toString()で調べると、nm μm単位での用紙サイズが得られた。
葉書とA4についてはMediaSizeも得られ、これからMediaSizeNameを再取得できた。
| MediaSizeName | MediaSize | MediaSizeName | X,Y |
|---|---|---|---|
| 指定 | .toString()値 | MediaSizeから再取得 | getX,getY |
| MediaSizeName.JAPANESE_POSTCARD | 100000x148000 μm | japanese-postcard | 100.0, 148.0 |
| MediaSizeName に CHO_3はない | |||
| MediaSizeName.ISO_A4 | 210000x297000 μm | iso-a4 | 210.0, 297.0 |
MediaSizeが定義されているのは、
MediaSize.Other.JAPANESE_POSTCARD, MediaSize.JIS.CHOU_3, MediaSize.ISO.A4 で、長3もある。
MediaSizeから、MediaSizeNameと、X,Yを得られることになっている。
MediaSizeName MediaSize#getMediaSizeName() float MediaSize#getX(int units) MediaSize⇨MediaSizeName MediaSize⇨MediaSize⇨X,Y
しかし、やってみると、X,Yはすべて求まるが、MediaSizeNameは長3封筒ではうまく行かない
| MediaSize | MediaSizeName | X,Y |
|---|---|---|
| 指定 | getMediaSizeName()で取得 | getX,getY |
| MediaSize.Other.JAPANESE_POSTCARD | japanese-postcard | 100.0, 148.0 |
| MediaSize.JIS.CHOU_3 | null | 120.0, 235.0 |
| MediaSize.ISO.A4 | iso-a4 | 210.0, 297.0 |
MediaSize.findMedia(120.0f, 235.0f, MediaPrintableArea.MM); というメソッドがあって、用紙のサイズからMediaSizeNameを求めるもの。これも葉書とA4ではうまく行くが、長3封筒ではだめ。
MediaSize.findMedia(100.0f,148.0f,MediaPrintableArea.MM); //japanese-postcard MediaSize.findMedia(120.0f,235.0f,MediaPrintableArea.MM); //null MediaSize.findMedia(210.0f,297.0f,MediaPrintableArea.MM); //iso-a4
493をA4に設定し直す。(もとは長形3号封筒)
Canon_MP493_series Canon_MP493_series (Idle, Accepting Jobs, Not Shared) Description: Canon MP493 series Location: Driver: Canon PIXUS MP493 - CUPS+Gutenprint v5.2.11 (color) Connection: usb://Canon/MP493%20series?serial=C0A92F&interface=1 Defaults: job-sheets=none, none media=iso_a4_210x297mm sides=one-sided
ppdの変更箇所を調べる(.0がバックアップファイル)
root@debian64:/etc/cups/ppd# diff Canon_MP493_series.ppd Canon_MP493_series.ppd.O 49c49 < *DefaultPageSize: A4 --- > *DefaultPageSize: w340h666 200c200 < *DefaultPageRegion: A4 --- > *DefaultPageRegion: w340h666 348c348 < *DefaultImageableArea: A4 --- > *DefaultImageableArea: w340h666 495c495 < *DefaultPaperDimension: A4 --- > *DefaultPaperDimension: w340h666 787c787 < *DefaultStpiShrinkOutput: Crop --- > *DefaultStpiShrinkOutput: Expand
666は235mm÷25.4×72で72dpiでのドット数。
A4に設定時

MediaSizeNameをA4に設定してそこから始めているプログラムである。
public void doDialogCheck(){
PrintRequestAttributeSet prset = new HashPrintRequestAttributeSet();
//MediaSizeName msname = MediaSizeName.JAPANESE_POSTCARD;
MediaSizeName msname = MediaSizeName.ISO_A4;
MediaSize mscho3 = MediaSize.JIS.CHOU_3;
MediaSize msothp = MediaSize.Other.JAPANESE_POSTCARD;
MediaSize msisoa4 = MediaSize.ISO.A4;
//MediaSize ms = mscho3; //x
//ms = msothp; //o
//ms = msmycho3; //x
//ms = msmyhaga; //o
//ms = msmya4; //x
//ms = msisoa4; //x
MediaSize ms = MediaSize.getMediaSizeForName(msname); //今回はA4
float mw = ms.getX(MediaPrintableArea.MM);
float mh = ms.getY(MediaPrintableArea.MM);
float leftmm = 5.1f;
float rightmm = 4.9f;
float topmm = 4.8f; //landscape前のTOP
float bottomm = 5.2f;
prset.add(new MediaPrintableArea(
leftmm, topmm,
(mw - leftmm - rightmm),
(mh - topmm - bottomm), MediaPrintableArea.MM));
//MediaSizeName mssname = MediaSize.findMedia(mw,mh,MediaPrintableArea.MM);
//System.out.println("(B)"+mssname);
//if (msname!=null) prset.add(msname);
prset.add(msname);
prset.add(OrientationRequested.PORTRAIT);
//prtreqattset0.add(OrientationRequested.REVERSE_LANDSCAPE);
PrintQuality pq = PrintQuality.NORMAL;
prset.add(pq);
System.out.println(prset.size());//test
Attribute[] atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
PrinterJob pj = PrinterJob.getPrinterJob();
pj.printDialog(prset);
System.out.println(prset.size());//test
atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
//pj.printDialog();
}
このプリンタが出るのはデフォルトプリンタだから

指定通りA4でマージンも指定通り

ここは設定できるものがない

ここを葉書にすると

プログラムの出力。
4 iso-a4 (5.1,4.8)->(200.0,287.0)mm portrait normal
葉書に設定したので2回目のダンプでこのようになる
5 japanese-postcard (5.1,4.8)->(90.0,138.0)mm portrait normal 1
この逆も画面で見る限りはうまく行っている様に見える
しかし実際の印刷はこの指定のとおりにならず、A4全体に広がり文字は崩れる。
MediaSizeからMediaSizeNameを得る方法は2つ。後者は大きさから近いものを選択してくれる期待をしたがそうではなかった。
MediaSize#getMediaSizeName() MediaSize.findMedia(mw,mh,MediaPrintableArea.MM);
public void doDialogCheck(){
PrintRequestAttributeSet prset = new HashPrintRequestAttributeSet();
MediaSize ms;
ms = msisoa4;
float mw = ms.getX(MediaPrintableArea.MM);
float mh = ms.getY(MediaPrintableArea.MM);
float leftmm = 5.1f;
float rightmm = 4.9f;
float topmm = 4.8f; //landscape前のTOP
float bottomm = 5.2f;
prset.add(new MediaPrintableArea(
leftmm, topmm,
(mw - leftmm - rightmm),
(mh - topmm - bottomm), MediaPrintableArea.MM));
MediaSizeName mssname = MediaSize.findMedia(mw,mh,MediaPrintableArea.MM);
System.out.println("(B)"+mssname);
if (mssname!=null) prset.add(mssname);
prset.add(OrientationRequested.PORTRAIT);
PrintQuality pq = PrintQuality.NORMAL;
prset.add(pq);
System.out.println(prset.size());//test
Attribute[] atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
PrinterJob pj = PrinterJob.getPrinterJob();
pj.printDialog(prset);
System.out.println(prset.size());//test
atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
}
名前も得られ、葉書への変更も可能(もちろんJava上に限っての話)
(B)iso-a4 4 iso-a4 (5.1,4.8)->(200.0,287.0)mm portrait normal 5 japanese-postcard (5.1,4.8)->(90.0,138.0)mm portrait normal 1
長3号はMediaSizeNameがないのでMediaSizeスタートする必然がある。
public void doDialogCheck(){
PrintRequestAttributeSet prset = new HashPrintRequestAttributeSet();
MediaSize mscho3 = MediaSize.JIS.CHOU_3;
MediaSize ms;
ms = mscho3; //x
float mw = ms.getX(MediaPrintableArea.MM);
float mh = ms.getY(MediaPrintableArea.MM);
float leftmm = 5.1f;
float rightmm = 4.9f;
float topmm = 4.8f; //landscape前のTOP
float bottomm = 5.2f;
prset.add(new MediaPrintableArea(
leftmm, topmm,
(mw - leftmm - rightmm),
(mh - topmm - bottomm), MediaPrintableArea.MM));
MediaSizeName mssname = MediaSize.findMedia(mw,mh,MediaPrintableArea.MM);
System.out.println("(B)"+mssname);
if (mssname!=null) prset.add(mssname);
prset.add(OrientationRequested.PORTRAIT);
PrintQuality pq = PrintQuality.NORMAL;
prset.add(pq);
System.out.println(prset.size());//test
Attribute[] atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
PrinterJob pj = PrinterJob.getPrinterJob();
pj.printDialog(prset);
System.out.println(prset.size());//test
atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
}
長3が見つからないのでA4になっていて、マージンも狂っている。

選択肢にJapanese long envelope #3 があるので選択してみる。

プログラムの出力では、Java側ではnullなので指定しないでダイアログを呼び出す。ただし用紙の大きさは得られているので、Setには正しく入力されている。(110.0,225.0)はマージンを引いた長3の値。
(B)null 3 portrait (5.1,4.8)->(110.0,225.0)mm normal
しかし、ダイアログには長3を設定しなかった分、A4になり、マージンが狂うのではないか。ダイアログでJapanese long envelope #3 に設定後、戻った時のSetは次のようになる。(19.944,162.95)は不明。A4でもポストカードでもない。長3に設定しなければA4になる。
5 1 portrait (5.1,4.8)->(19.944,162.95)mm normal Japanese long envelope #3
JavaのマニュアルのMediaSizeNameには名前しかない。MediaSizeには長さが書いてあるので、必要なものだけ転記しておく。
すべて、static MediaSize なフィールド値
10x13などのxは大文字小文字で揺れがある。
| MediaSize.ISO (主要なもののみ) | MediaSizeName | ||
|---|---|---|---|
| A3 | ISOのA3サイズ | 横297 mm、縦420 mm | ISO_A3 |
| A4 | ISOのA4サイズ | 横210 mm、縦297 mm | ISO_A4 |
| DESIGNATED_LONG | ISO 指定のロングサイズ | 横 110 mm、縦 220 mm | ISO_DESIGNATED_LONG |
| MediaSize.NA | MediaSizeName | ||
| LEGAL | 北米規格のリーガルサイズ | 横 8.5 inch、縦 14 inch | NA_LEGAL |
| LETTER | 北米規格のレターサイズ | 横 8.5 inch、縦 11 inch | NA_LETTER |
| NA_10x13_ENVELOPE | 北米規格の横 10 inch、縦 13 inch の封筒サイズ | NA_10x13_ENVELOPE | |
| NA_10x14_ENVELOPE | 北米規格の横 10 inch、縦 14 inch の封筒サイズ | NA_10x14_ENVELOPE | |
| NA_10X15_ENVELOPE | 北米規格の横 10 inch、縦 15 inch の封筒サイズ | NA_10X15_ENVELOPE | |
| NA_5X7 | 北米規格の横 5 inch、縦 7 inch の用紙 | NA_5X7_ENVELOPE | |
| NA_6X9_ENVELOPE | 北米規格の横 6 inch、縦 9 inch の封筒サイズ | NA_6X9_ENVELOPE | |
| NA_7X9_ENVELOPE | 北米規格の横 7 inch、縦 9 inch の封筒サイズ | NA_7X9_ENVELOPE | |
| NA_8X10 | 北米規格の横 8 inch、縦 10 inch の用紙 | NA_8X10_ENVELOPE | |
| NA_9x11_ENVELOPE | 北米規格の横 9 inch、縦 11 inch の封筒サイズ | NA_9x11_ENVELOPE | |
| NA_9x12_ENVELOPE | 北米規格の横 9 inch、縦 12 inch の封筒サイズ | NA_9x12_ENVELOPE | |
| NA_NUMBER_10_ENVELOPE | 北米規格の 10 号ビジネス封筒サイズ | 横 4.125 inch、縦 9.5 inch | NA_NUMBER_10_ENVELOPE |
| NA_NUMBER_11_ENVELOPE | 北米規格の 11 号ビジネス封筒サイズ | 横 4.5 inch、縦 10.375 inch | NA_NUMBER_11_ENVELOPE |
| NA_NUMBER_12_ENVELOPE | 北米規格の 12 号ビジネス封筒サイズ | 横 4.75 inch、縦 11 inch | NA_NUMBER_12_ENVELOPE |
| NA_NUMBER_14_ENVELOPE | 北米規格の 14 号ビジネス封筒サイズ | 横 5 inch、縦 11.5 inch | NA_NUMBER_14_ENVELOPE |
| NA_NUMBER_9_ENVELOPE | 北米規格の 9 号ビジネス封筒サイズ | 横 3.875 inch、縦 8.875 inch | NA_NUMBER_9_ENVELOPE |
| MediaSize.Other | MediaSizeName | ||
| EXECUTIVE | エグゼクティブサイズ | 横 7.25 inch、縦 10.5 inch | EXECUTIVE |
| FOLIO | フォリオサイズ | 横 8.5 inch、縦 13 inch | FOLIO |
| INVOICE | 請求書サイズ | 横 5.5 inch、縦 8.5 inch | INVOICE |
| ITALY_ENVELOPE | イタリアの封筒サイズ | 横 110 mm、縦 230 mm | ITALY_ENVELOPE |
| JAPANESE_DOUBLE_POSTCARD | 日本の往復はがきサイズ | 横 200 mm、縦 148 mm | JAPANESE_DOUBLE_POSTCARD |
| JAPANESE_POSTCARD | 日本のはがきサイズ | 横 100 mm、縦 148 mm | JAPANESE_POSTCARD |
| LEDGER | 帳簿サイズ | 横 11 inch、縦 17 inch | LEDGER |
| MONARCH_ENVELOPE | 封筒サイズ(キングサイズ) | 横 3.87 inch、縦 7.5 inch | MONARCH_ENVELOPE |
| PERSONAL_ENVELOPE | 封筒サイズ(パーソナルサイズ) | 横 3.625 inch、縦 6.5 inch | PERSONAL_ENVELOPE |
| QUARTO | クアトロサイズ | 横 8.5 inch、縦 10.83 inch | QUARTO |
| TABLOID | タブロイドサイズ | 横 11 inch、縦 17 inch | TABLOID |
| MediaSize.JIS (封筒のみ) | MediaSizeName | ||
| CHOU_1 | JISの長型1号封筒サイズ | 横142 mm、縦332 mm | |
| CHOU_2 | JISの長型2号封筒サイズ | 横119 mm、縦277 mm | |
| CHOU_3 | JISの長型3号封筒サイズ | 横120 mm、縦235 mm | |
| CHOU_30 | JISの長型30号封筒サイズ | 横92 mm、縦235 mm | |
| CHOU_4 | JISの長型4号封筒サイズ | 横90 mm、縦205 mm | |
| CHOU_40 | JISの長型40号封筒サイズ | 横90 mm、縦225 mm | |
| KAKU_0 | JISの角型0号封筒サイズ | 横287 mm、縦382 mm | |
| KAKU_1 | JISの角型1号封筒サイズ | 横270 mm、縦382 mm | |
| KAKU_2 | JISの角型2号封筒サイズ | 横240 mm、縦332 mm | |
| KAKU_20 | JISの角型20号封筒サイズ | 横229 mm、縦324 mm | |
| KAKU_3 | JISの角型3号封筒サイズ | 横216 mm、縦277 mm | |
| KAKU_4 | JISの角型4号封筒サイズ | 横197 mm、縦267 mm | |
| KAKU_5 | JISの角型5号封筒サイズ | 横190 mm、縦240 mm | |
| KAKU_6 | JISの角型6号封筒サイズ | 横162 mm、縦229 mm | |
| KAKU_7 | JISの角型7号封筒サイズ | 横142 mm、縦205 mm | |
| KAKU_8 | JISの角型8号封筒サイズ | 横119 mm、縦197 mm | |
| KAKU_A4 | JISの角型A4封筒サイズ | 横228 mm、縦312 mm | |
| YOU_1 | JISの洋型1号封筒サイズ | 横120 mm、縦176 mm | |
| YOU_2 | JISの洋型2号封筒サイズ | 横114 mm、縦162 mm | |
| YOU_3 | JISの洋型3号封筒サイズ | 横98 mm、縦148 mm | |
| YOU_4 | JISの洋型4号封筒サイズ | 横105 mm、縦235 mm | |
| YOU_5 | JISの洋型5号封筒サイズ | 横95 mm、縦217 mm | |
| YOU_6 | JISの洋型6号封筒サイズ | 横98 mm、縦190 mm | |
| YOU_7 | JISの洋型7号封筒サイズ | 横92 mm、縦165 mm | |
なお、型の字を使っているが、形が正しいようだ。
MediaSizeにあって、MediaSizeNameにないものは、JISの封筒関係の24件だけ。
逆にMediaSizeNameにあってMediaSizeにないものが、ISOのC0,C1,C2の3件。
MediaSizeには94、MediaSizeNameには73の登録がある。
JIS.CHOU_3に近い大きさの封筒。Javaにあってもプリンタ(特に日本のだからか)に無い場合もある。
| MediaSizeName | inch | mm |
|---|---|---|
| MediaSize.JIS.CHOU_3 | 120mm 235mm | |
| MediaSize.Other.ITALY_ENVELOPE | 110mm 230mm | |
| MediaSize.NA.NA_NUMBER_10_ENVELOPE | 横 4.125 inch、縦 9.5 inch | 104.775mm 241.3mm |
| MediaSize.NA.NA_6X9_ENVELOPE | 横 6 inch、縦 9 inch | 152.4mm 228.6mm |
PrintServiceのこと。
PrintRequestAttributeSetのインスタンスを観察する中、プリンタのもつ選択肢が反映されているようにも見えるが、確信が持てない。また、同じ選択肢が2つずつ現れる場合もある。
こちらで設定したPrintRequestAttributeSetを用いての印刷はこちらから一方的にリクエストすることになるイメージがある。
まだ使用経験があまりないが、プリントサービスを使って設定にあったプリンタを探すというアプローチなら、うまく要求が伝わるかもしれない。
ということで、やってみる。プログラムの流れは、
(1)設定したPrintRequestAttributeSetのインスタンスで、サービスをlookupする。
(2)0番を使って(defaultを使う試用例もあるが)ダイアログを出して確認
(3)選択したservice(0番)からDocPrintJobのインスタンスを生成し
(4)printableなクラス(自作)から生成したSimpleDocを用意し、
(5)DocPrintJobのインスタンスにとPrintRequestAttributeSetとともに渡してprintさせる
public void doDialogCheck(){
DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
PrintRequestAttributeSet prset = new HashPrintRequestAttributeSet();
MediaSize mscho3 = MediaSize.JIS.CHOU_3;
MediaSize msothp = MediaSize.Other.JAPANESE_POSTCARD; //ここから選択
MediaSize msisoa4 = MediaSize.ISO.A4;
MediaSize msotie = MediaSize.Other.ITALY_ENVELOPE;//no
MediaSize msnan10 = MediaSize.NA.NA_NUMBER_10_ENVELOPE;
MediaSize msna6x9 = MediaSize.NA.NA_6X9_ENVELOPE;
MediaSize ms;
ms = msothp; //ここを変更してテストする
float mw = ms.getX(MediaPrintableArea.MM);
float mh = ms.getY(MediaPrintableArea.MM);
float leftmm = 5.1f;
float rightmm = 4.9f;
float topmm = 4.8f; //landscape前のTOP
float bottomm = 5.2f;
prset.add(new MediaPrintableArea(
leftmm, topmm,
(mw - leftmm - rightmm),
(mh - topmm - bottomm), MediaPrintableArea.MM));
prset.add(OrientationRequested.PORTRAIT);
MediaSizeName mssname = MediaSize.findMedia(mw,mh,MediaPrintableArea.MM);
System.out.println("(A)"+mssname);
if (mssname!=null) prset.add(mssname);
System.out.println("(B)"+ms.getMediaSizeName());
System.out.println("BeforeLookup : "+prset.size());//test
Attribute[] atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
PrintService[] psvs = PrintServiceLookup.lookupPrintServices(flavor,prset);
PrintService dpsv = PrintServiceLookup.lookupDefaultPrintService();
for (int i=0;psvs.length>i;i++){
System.out.println("sv("+i+") "+psvs[i]);
}
PrintService service =
ServiceUI.printDialog(null, 400, 400,psvs, psvs[0], flavor, prset);
System.out.println("AfterSelectService : "+prset.size());//test
atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
if (service != null) {
DocPrintJob job = service.createPrintJob();
SimpleDoc doc = new SimpleDoc(new SomethingPrintable01(), flavor, null);
try {
job.print(doc, prset);
}catch (PrintException e){
System.err.println(e.getMessage());
}
}
}
印刷もするが、画面出力は次の通り
PrintRequestAttributeSetではJAPANESE_POSTCARD;を指定。
プリンタ(Canon_MP493_series)のCUPSデフォルトはA4。
PrintRequestAttributeSetの確認ではうまく行っているが、印刷はだめ。
(A)japanese-postcard (B)japanese-postcard BeforeLookup : 3 portrait (5.1,4.8)->(90.0,138.0)mm japanese-postcard sv(0) IPP Printer : Canon_MP493_series sv(1) IPP Printer : EPSON_EPSON_PX-1700F AfterSelectService : 4 portrait (5.1,4.8)->(90.0,138.0)mm japanese-postcard 1
印刷は、はみ出すとか、拡大縮小されるとか、位置が狂うとかではなく、話にならない。
あとで判明するが、次の項目を設定しなければ「Shrink」になっていて、ハガキがA4に拡大される。逆にプリンタの設定範囲以上に書かせようとすると縮小される。このため郵便番号に数字が合わなくなりCropに変更していたのが下記のスキャンを産んだ。Expandでも同様に点々になる。
Shrink Page If Necessary to Fit Borders: ・Shrink (print the whole page) ・Crop (preserve dimensions) ・Expand (use maximum page area)
どちらにしても、MediaSizeNameの問題のないA4と葉書ですでにうまくいかない。
LibreOfficeではA4デフォルトのプリンタに印刷できるし、EPSONのPX-1700FでA4,A3,B5の間でもあった(この時はShrinkだった)ことなので、OS,プリンタ,の問題ではなくJavaの問題ではないかと疑われる。

プリントサービスはリクエストに合致したサービスを検索するという触れ込みだったので、PrintRequestAttributeSetを先に用意したが、サービスを選んでから設定してprint時に指定する例が多く見られる。これはこうしなければうまく行かないということなのかもしれない。
MediaSizeNameに長形3号がないのも問題ではあるが、名前のあるものを使ってもJavaから用紙の変更ができないので、最終手段。A4,葉書、長形3号のそれぞれのプリンタの設定を用意してこれを切り替えることを試みる。
/etc/cups/ppdにファイルがある。これはプリンタの対応する用紙や解像度などの条件が書かれたテキストファイルで用紙のデフォルトもここに書かれている。上の「CUPSのデフォルト設定」に前回A4にしたときの様子がある。
root@debian64:~# ls -l /etc/cups 合計 72 -rw-r--r-- 1 root root 16433 1月 19 2017 cups-browsed.conf -rw-r--r-- 1 root root 2931 2月 23 2018 cups-files.conf -rw-r--r-- 1 root root 4629 7月 4 12:58 cupsd.conf drwxr-xr-x 2 root root 4096 2月 23 2018 interfaces drwxr-xr-x 2 root lp 4096 11月 29 11:35 ppd -rw------- 1 root lp 2590 11月 29 11:37 printers.conf -rw------- 1 root lp 2995 11月 29 11:36 printers.conf.O -rw-r--r-- 1 root root 240 7月 4 13:00 raw.convs -rw-r--r-- 1 root root 211 7月 4 13:00 raw.types -rw-r--r-- 1 root root 142 2月 23 2018 snmp.conf drwx------ 2 root lp 4096 7月 4 12:58 ssl -rw-r----- 1 root lp 390 11月 29 22:54 subscriptions.conf -rw-r----- 1 root lp 390 11月 29 22:08 subscriptions.conf.O
その中は
-rw-r----- 1 root lp 127373 11月 29 10:57 Canon_MP493_series.ppd -rw-r----- 1 root lp 127347 11月 18 17:44 Canon_MP493_series.ppd.O -rw-r----- 1 root lp 51269 7月 14 08:56 EPSON_EPSON_PX-1700F.ppd -rw-r----- 1 root lp 51269 7月 10 23:36 EPSON_EPSON_PX-1700F.ppd.O
これをコピーして増やしても認識されない。その方法を探るうち新規プリンタの追加でppdファイルを指定すれば良いことを思いついた。つまり、ppdファイルを適当な所にコピーしておき、追加時にプリンタ名を適当な名前にしてからドライバ設定でそのppdファイルを指定すると/etc/cups/ppdにコピーされる。続けて言われるままにデフォルトを設定して用紙と印字品質などを変更する。

設定が終わると現在値が示される

というわけで普通のプリンタと、長形3号用、A4用、ハガキ用の4つができた。
Canon_MP493_series Canon_MP493_series (Idle, Accepting Jobs, Not Shared) Description: Canon MP493 series Location: Driver: Canon PIXUS MP493 - CUPS+Gutenprint v5.2.11 (color) Connection: usb://Canon/MP493%20series?serial=C0A92F&interface=1 Defaults: job-sheets=none, none media=iso_a4_210x297mm sides=one-sided
Canon_MP493_chou3 Canon_MP493_chou3 (Idle, Accepting Jobs, Not Shared) Description: Canon MP493 chou3ppd Location: Driver: Canon PIXUS MP493 - CUPS+Gutenprint v5.2.11 (color) Connection: usb://Canon/MP493%20series?serial=C0A92F&interface=1 Defaults: job-sheets=none, none media=om_w340h666_119.94x234.95mm sides=one-sided
Canon_MP493_A4 Canon_MP493_A4 (Idle, Accepting Jobs, Not Shared) Description: Canon MP493 A4ppd Location: Driver: Canon PIXUS MP493 - CUPS+Gutenprint v5.2.11 (color) Connection: usb://Canon/MP493%20series?serial=C0A92F&interface=1 Defaults: job-sheets=none, none media=iso_a4_210x297mm sides=one-sided
Canon_MP493_hagaki Canon_MP493_hagaki (Idle, Accepting Jobs, Not Shared) Description: Canon MP493 hagaki Location: Driver: Canon PIXUS MP493 - CUPS+Gutenprint v5.2.11 (color) Connection: usb://Canon/MP493%20series?serial=C0A92F&interface=1 Defaults: job-sheets=none, none media=jpn_hagaki_100x148mm sides=one-sided
PX-1700Fと合わせて5つになる。

A4用が[0]なのでこれが最初に出る。

用紙設定はchou3なのだが、このMediaSizeNameを指定できないのでデフォルトのA4になっている。マージンの左と上は指定通りで指定の幅と高さをA4から引いたものが右と下なのだろう

用紙はそのままにして、プリンタをchou3にしたところ、うまく印刷できた。用紙設定も変えるとマージンも変えなければ印刷範囲が異常に狭くなってしまう。目視で変更を確認できないのは不満ではあるが、用紙設定を手動で変えなくてもいいのであれば、これはこれでも容認できる。

ちなみにプリンタをchou3にしなくてもうまく行くこともある。これはA4のままマージンを多く調整しているので、A4のまま印刷してもちょうどよいということらしい。ただし、A4の左側に印刷されるので、小さな紙はセンターに配置するMP493ではまずい。ハガキは齟齬が生ずるのでだめ。
(1) DocFlavor と PrintRequestAttributeSet のインスタンスを引数にプリンタを探す。
PrintService[] psvs = PrintServiceLookup.lookupPrintServices(flavor,prset);
デフォルトの用紙で選択するのではなく対応可能な用紙での選択なのですべてが選択される。A3など明らかに対応不可な条件では候補に入らないので、条件は伝わっているようではある。
(2) 指定した用紙をデフォルトにするプリンタサービスを自動で特定する
デフォルトの用紙を知る方法や、用紙の名前の統一など不確実なところが多いので、プリンタ名に加えた文字列で識別してサービスから一つを選び出す。
int cdsv = 0;
for (int i=0;psvs.length>i;i++){
if(psvs[i].getName().contains(paperkey)) cdsv= i;
}
paperkeyは文字列で、"A4","hagaki","chou3" のうちのどれか。
これで、psvs[cdsv]が用紙が合致したプリンタということになる。
(3) ダイアログを表示する。
本来は合致したプリンタ候補からプリンタを選ぶためのものらしい。DocFlavor と PrintRequestAttributeSet のインスタンスを再び指定しているのは冗長な気もする。
400,400は表示する座標の指定。画面中央や相対指定はない。
PrintService service = ServiceUI.printDialog(null, 400, 400, psvs, psvs[cdsv], flavor, prset);
(4) 印刷する
サービスからジョブを作り、SimpleDocをPrintableから作って、3度PrintRequestAttributeSetのインスタンスを添えて印刷する。実際にはtry-catchが必要。
3度なのは、指定を反映したいということから、しつこく要求した結果である。どうしてもだめということで、デフォルトの異なるプリンタサービスを用意したのであって、そうであるならば3度もいらないかもしれない。
DocPrintJob job = service.createPrintJob(); SimpleDoc doc = new SimpleDoc(printable, flavor, null); job.print(doc, prset);
PrintServiceAttributeSetは
AttributsfromService : 6 0 Canon MP493 A4ppd Canon_MP493_A4 accepting-jobs supported not-attempted
とプリンタの状態などのことで、用紙には関わっていない
public void doDialogCheck(String paperkey){
DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
PrintRequestAttributeSet prset = new HashPrintRequestAttributeSet();
MediaSize ms = MediaSize.ISO.A4;
if(paperkey.equals("hagaki")) ms = MediaSize.Other.JAPANESE_POSTCARD;
else if (paperkey.equals("chou3")) ms = MediaSize.JIS.CHOU_3;
float mw = ms.getX(MediaPrintableArea.MM);
float mh = ms.getY(MediaPrintableArea.MM);
float leftmm = 5.1f;
float rightmm = 4.9f;
float topmm = 4.8f; //landscape前のTOP
float bottomm = 5.2f;
prset.add(new MediaPrintableArea(
leftmm, topmm,
(mw - leftmm - rightmm),
(mh - topmm - bottomm), MediaPrintableArea.MM));
prset.add(OrientationRequested.PORTRAIT);
MediaSizeName mssname = MediaSize.findMedia(mw,mh,MediaPrintableArea.MM);
if (mssname!=null) prset.add(mssname);
System.out.println("(A)"+mssname);
System.out.println("(B)"+ms.getMediaSizeName());
System.out.println("BeforeLookup : "+prset.size());//test
Attribute[] atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
PrintService[] psvs = PrintServiceLookup.lookupPrintServices(flavor,prset);
PrintService dpsv = PrintServiceLookup.lookupDefaultPrintService();
int cdsv = 0;
for (int i=0;psvs.length>i;i++){
if(psvs[i].getName().contains(paperkey)) cdsv= i;
System.out.println("sv("+i+") "+psvs[i].getName());
}
PrintServiceAttributeSet svset = psvs[cdsv].getAttributes();
System.out.println("AttributsfromService : "+svset.size());//test
atbs= svset.toArray();
for (int i=0;svset.size()>i;i++){
System.out.println(atbs[i]);
}
PrintService service = ServiceUI.printDialog(null, 400, 400,
psvs, psvs[cdsv], flavor, prset);
System.out.println("AfterSelectService : "+prset.size());//test
atbs= prset.toArray();
for (int i=0;prset.size()>i;i++){
System.out.println(atbs[i]);
}
//PrintQuality pq = PrintQuality.NORMAL;
//prset.add(pq);
if (service != null) {
DocPrintJob job = service.createPrintJob();//++
SimpleDoc doc = new SimpleDoc(new SomethingPrintable01(), flavor, null);
try {
job.print(doc, prset); //psv
}catch (PrintException e){ //psv
System.err.println(e.getMessage());
}
}
}
実行結果 A4
adachi@debian64:/media/adachi/S1T/java/prtest$ java TestX (A)iso-a4 (B)iso-a4 BeforeLookup : 3 (5.1,4.8)->(200.0,287.0)mm portrait iso-a4 sv(0) Canon_MP493_A4 sv(1) Canon_MP493_chou3 sv(2) Canon_MP493_hagaki sv(3) Canon_MP493_series sv(4) EPSON_EPSON_PX-1700F AttributsfromService : 6 0 Canon MP493 A4ppd Canon_MP493_A4 accepting-jobs supported not-attempted AfterSelectService : 4 (5.1,4.8)->(200.0,287.0)mm portrait 1 iso-a4
実行結果 葉書
adachi@debian64:/media/adachi/S1T/java/prtest$ java TestX (A)japanese-postcard (B)japanese-postcard BeforeLookup : 3 (5.1,4.8)->(90.0,138.0)mm portrait japanese-postcard sv(0) Canon_MP493_A4 sv(1) Canon_MP493_chou3 sv(2) Canon_MP493_hagaki sv(3) Canon_MP493_series sv(4) EPSON_EPSON_PX-1700F AttributsfromService : 6 0 Canon MP493 hagaki Canon_MP493_hagaki accepting-jobs supported not-attempted AfterSelectService : 4 (5.1,4.8)->(90.0,138.0)mm portrait 1 japanese-postcard
実行結果 長3
adachi@debian64:/media/adachi/S1T/java/prtest$ java TestX (A)null (B)null BeforeLookup : 2 (5.1,4.8)->(110.0,225.0)mm portrait sv(0) Canon_MP493_A4 sv(1) Canon_MP493_chou3 sv(2) Canon_MP493_hagaki sv(3) Canon_MP493_series sv(4) EPSON_EPSON_PX-1700F AttributsfromService : 6 accepting-jobs 0 Canon_MP493_chou3 not-attempted supported Canon MP493 chou3ppd AfterSelectService : 4 (5.1,4.8)->(110.0,225.0)mm portrait Japanese long envelope #3 1
http://www.cresc.co.jp/tech/java/jps/JPS.htm https://www.eeb.co.jp/wordpress/?p=384
結局、用紙の指定がうまく行かなかったので、Service中心で考えていくメリットがなくなった。実はもっとクラスServiceUIFactoryまで考えるとできる可能性もあるのだが、今回はここまでにしておく。
すると、ServiceUI.printDialog()の位置指定やSimpleDocの利用の意味が薄れる。
そこで後半をDocPrintJobでなく、PrinterJobに戻すことを考えてみる
(1)から(2)までは同じにしておく
(1) DocFlavor と PrintRequestAttributeSet のインスタンスを引数にプリンタを探す。
PrintService[] psvs = PrintServiceLookup.lookupPrintServices(flavor,prset);
デフォルトの用紙で選択するのではなく対応可能な用紙での選択なのですべてが選択される。A3など明らかに対応不可な条件では候補に入らないので、条件は伝わっているようではある。
DocFlavorなどを要求しない方法もある。今回はこれでもできる。
PrintService[] psvs = PrinterJob.lookupPrintServices();
(2) 指定した用紙をデフォルトにするプリンタサービスを自動で特定する
デフォルトの用紙を知る方法や、用紙の名前の統一など不確実なところが多いので、プリンタ名に加えた文字列で識別してサービスから一つを選び出す。
int cdsv = 0;
for (int i=0;psvs.length>i;i++){
if(psvs[i].getName().contains(paperkey)) cdsv= i;
}
paperkeyは文字列で、"A4","hagaki","chou3" のうちのどれか。
これで、psvs[cdsv]が用紙が合致したプリンタということになる。
(3) ダイアログを表示する。
PrinterJob.getPrinterJob()でまず、何もないところからPrinterJobを取得し、それにsetPrintService()で(2)で選んだサービスを結びつける。
PrinterJob#printDialog()でダイアログを出す。ここからtry-catchが必要
pj.setPrintService(psvs[cdsv]);
pj.setPrintable(new SomethingPrintable01());
if (pj.printDialog(prset)) {...
(4) 印刷する
SimpleDocもいらず、単純になる。
pj.print( prset );
途中の確認のための出力を削ったので見通しが良くなっている。
public void doDialogCheck2(String paperkey){
DocFlavor flavor = DocFlavor.SERVICE_FORMATTED.PRINTABLE;
PrintRequestAttributeSet prset = new HashPrintRequestAttributeSet();
MediaSize ms = MediaSize.ISO.A4;
if(paperkey.equals("hagaki")) ms = MediaSize.Other.JAPANESE_POSTCARD;
else if (paperkey.equals("chou3")) ms = MediaSize.JIS.CHOU_3;
float mw = ms.getX(MediaPrintableArea.MM);
float mh = ms.getY(MediaPrintableArea.MM);
float leftmm = 5.1f;
float rightmm = 4.9f;
float topmm = 4.8f; //landscape前のTOP
float bottomm = 9.2f;
prset.add(new MediaPrintableArea(
leftmm, topmm,
(mw - leftmm - rightmm),
(mh - topmm - bottomm), MediaPrintableArea.MM));
prset.add(OrientationRequested.PORTRAIT);
MediaSizeName mssname = MediaSize.findMedia(mw,mh,MediaPrintableArea.MM);
if (mssname!=null) prset.add(mssname);
PrintService[] psvs = PrintServiceLookup.lookupPrintServices(flavor,prset);
//PrintService[] services = PrinterJob.lookupPrintServices();
if (psvs.length > 0) {
int cdsv = 0;
for (int i=0;psvs.length>i;i++){
if(psvs[i].getName().contains(paperkey)) cdsv= i;
}
PrinterJob pj = PrinterJob.getPrinterJob();
try {
pj.setPrintService(psvs[cdsv]);
pj.setPrintable(new SomethingPrintable01());
if (pj.printDialog(prset)) {
pj.print( prset );
//job.print(doc, prset);
}
}catch (PrinterException e){ //PrintException e){
System.err.println(e.getMessage());
} //try-catch
}else{ //if service found
System.out.println("service not found");
} //if service found
}
用紙の目途がついて印字品質も試してみた。解像度は調べるのが面倒なので自動とし、Qualityでできないかとやってみたが、NORMAL を HIGH にするとプリンタサービスが見つからなくなる。
PrintQuality pq = PrintQuality.HIGH; //NORMAL; prset.add(pq);
ダイアログでも常に灰色になって選択できない。
ブラウザでのCUPSの設定ではQualityはNORMALとMANUAL。設定を変えても変わった様子がない。用紙を plain paper を inkjet hagaki にするなどすると印字時間が長くなって切り替わったことがわかる。Java側からはできていない。
上記で使ったprintableなクラス。印刷内容はこちらで決まる
前に作ったものの流用。今回はmainを使っていない。
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.print.PrinterJob;
import java.awt.print.Printable;
import java.awt.print.PageFormat;
import java.awt.print.PrinterException;
import java.awt.BasicStroke;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
public class SomethingPrintable01 implements Printable {
@Override
public int print(Graphics g, PageFormat pf, int pageIndex) {
if (pageIndex != 0) return NO_SUCH_PAGE;
Graphics2D g2 = (Graphics2D)g;
g2.drawString("文字を書きます", 72, 120); //文字の印字
g2.drawLine(72,140,288,160); //線の描画
double x = pf.getImageableX();
double y = pf.getImageableY();
double w = pf.getImageableWidth();
double h = pf.getImageableHeight();
Rectangle2D.Double rectg = new Rectangle2D.Double(x, y, w, h-y);
g2.draw(rectg);
double pt2mm = 25.4d/72;
int x2 = (int)x*2;
g2.drawString(String.format("x:%7.2fmm y :%7.2fmm",x*pt2mm,y*pt2mm),x2,240);
g2.drawString(String.format("w:%7.2fmm h-y:%7.2fmm",w*pt2mm,(h-y)*pt2mm),x2,300);
return PAGE_EXISTS;
}
public static void main(String[] args) {
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPrintable(new SomethingPrintable01());
if (pj.printDialog()) {
try { pj.print(); }
catch (PrinterException e) {
System.out.println(e);
}
}
}
}
プリンタは Canon MP493 後部の手差しトレイから紙を入れる。最大はA4。小さい紙は中央に寄せてセットするタイプ。以下A4,長3,葉書で印刷したが、印刷範囲の比較のため、すべてA4用紙に出力した。