はじめに
住所を印刷する場合を考えます。AdjustString.java(第4回を参照)で指定範囲に書ききれない場合には、次の行に書くことができましたが、住所の場合は例えば203という数字が2と03との間で区切られてはまずいわけです。ここで丁目とか番地とかで自動的に区切ることができないかということを考えてみます。世間的には住所をあらかじめ2つから3つの部分に分けて登録するというのが定番のようではありますが、あえて挑戦してみます。今回は横書きのみを扱います。年賀状によくある縦書きは次回にまわします。
先に印刷プレビューをできるようにします。うまく区切られるかが印刷しなくとも画面で確認できるのは便利です。
横書き住所とその簡易プレビュー
まず、住所は1人分のみを仮に入れて試作します。
住所は横書きとして既出のAdjustString.javaを使って書き切れるまで改行していきます。
プレビューは、printableなインターフェースを持たせて作ったクラスを、さらにJPanelを継承したものとして、自分自身にも描画します。具体的にはpaintComponent()メソッドを加えて、印刷に使うprint()メソッドに書いていた描画内容を独立したメソッドにして、paintComponent()とprint()の両方から呼び出すようにします。
JPanelの表示には取りあえずJOptionPane.showOptionDialog()を使って簡単に済ませます。これは後で改良します。
/** PrintableWithPreview01.java
AdjustString.javaを使い、1行に入り切らない時は2行に分けます。
プレビューをJPanelとJOptionPane.showOptionDialogで実現します。
どちらも仮の仕様で、すぐに改良する予定です。
2019-11-06 Adachi Junichi
*/
import java.awt.*;
import java.awt.print.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
import javax.swing.*; //追加します
public class PrintableWithPreview01 extends JPanel implements Printable {//①
String zip;
String addr;
String name;
float mm2pt = 72/25.4f;
public PrintableWithPreview01(String zip,String addr,String name){
this.zip = zip;
this.addr = addr;
this.name = name;
setBackground(Color.white);
}
public void setPanelSize(float w,float h){ //⑤
Dimension d = new Dimension();
d.setSize(w*mm2pt,h*mm2pt);
setPreferredSize(d);
}
@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){ //④
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.BLACK);
//w:width ; v:virtical width ; pch:pitch
float xzip1 = 44.0f;
float xzip2 = 66.0f;
float xpch1 = 13.9f/2;
float xpch2 = 20.5f/3;
float wzbox = 5.7f;
float yzip = 20.3f;
float vzip = 8.3f;
g2.setFont(new Font("SansSerif", Font.PLAIN, 16));
float padb = 1.5f; //yzipそのままだと下線に接するのでもどす値(mm)
AdjustString ads;
for(int i=0; 3>i; i++){
ads = new AdjustString(g2, zip.substring(i,i+1),wzbox);
ads.drawCenter(xzip1 + xpch1*i, yzip - padb);
}
for(int i=0; 4>i; i++){
ads = new AdjustString(g2, zip.substring(i+4,i+1+4),wzbox);
ads.drawCenter(xzip2 + xpch2*i, yzip - padb);
}
//float charh = g2.getFontMetrics().getFont().getSize()/mm2pt;
float xaddr = xzip1-2.0f;
float yaddr = yzip +vzip*1.5f;
float ypch = vzip;
float waddr = xpch1*3+xpch2*4;
g2.setFont(new Font("SansSerif", Font.PLAIN, 14));
ads = new AdjustString(g2,addr,waddr);
ads.drawLeft(xaddr, yaddr);
int ct = 0;
while(ads.hasNext()){
ads = new AdjustString(g2, addr ,waddr, ads.getNextPt());
ads.drawRight(xaddr, yaddr + ypch*(++ct));
}
g2.setFont(new Font("SansSerif", Font.PLAIN, 16));
ads = new AdjustString(g2,name+"様",waddr);
ads.drawKintou(xaddr, yaddr + ypch*(++ct + 0.5f));
}
public static void main(String[] args) {
String zip = "036-1234";
String addr = "神奈川県相模原市南区南台6-8-10 三上ハイツ304";
String name = "齋藤梨花";
PrintableWithPreview01 pable = new PrintableWithPreview01(zip,addr,name);
PrintRequestAttributeSet rqset = new HashPrintRequestAttributeSet();
//MediaSizeName msname = MediaSizeName.ISO_A4; //A4用紙
MediaSizeName msname = MediaSizeName.JAPANESE_POSTCARD;
rqset.add(msname);
MediaSize msize = MediaSize.getMediaSizeForName(msname);
float mwidth = msize.getX(MediaPrintableArea.MM);
float mheight = msize.getY(MediaPrintableArea.MM);
float L = 5.1f;
float R = 5.2f;
float T = 5.3f;
float B = 5.4f;
rqset.add(new MediaPrintableArea(
L, T, (mwidth - L - R), (mheight - T - B),
MediaPrintableArea.MM));
pable.setPanelSize(mwidth,mheight);
String[] goOrNot = {"印刷","中止"};
int rev = JOptionPane.showOptionDialog( //⑥
null, pable,
"印刷プレビュー",
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE,
null,goOrNot,goOrNot[0]
);
//rev:"印刷":0,"中止":1
if (rev!=0) System.exit(0);
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPrintable(pable);
if (pj.printDialog(rqset)) {
try { pj.print(rqset); }
catch (PrinterException e) {
System.err.println(e);
}
}
}
}
このプログラムのプレビューです。
プレビューの追加部分の詳しい解説
丸付き数字はプログラム中の部分を指します。
① extends JPanelを加えます
コンストラクタはデータを受け取る都合だけです。プレビューには関わりません。
② paintComponent()メソッドを加えます
JPanelがもともと持っているものをオーバーライドして自身にプレビュー画面を描画します。このようにしておくとウィンドウの上下関係が変わったりしてJPanelの再描画が必要になった時に自動的に描き直されます。
実際の描画は④のdrawPage()で行います。
③ print()メソッドも④のdrawPage()を呼び出すだけにします。
印刷の時に呼び出されるのがprint()、プレビューで呼び出されるのがpaintComponent()で、両方から共通のdrawPage()を呼び出すことにより、描画内容を共通にできるというわけです。
④ そのdrawPage()です。
描画の本体(均等割付、罫線....)です。これまでは、print()メソッドの中に直接書いたものをこちらに移動しました。
paintComponent()からもprint()からも、Graphics gを引数に呼び出すようにし、このGraphicsに描画します。この仕組みのおかげで、画面表示用と印刷用で違いを気にする必要がなくなります。
setRenderingHint()を加えています。アンチエイリアスの適用をして滑らかな描画をします。Windowsでは指定しなくてもある程度の平滑化がされていますが、Linuxでは必要です。
⑤ JPanelの大きさの希望値をセットします
大きさをセットしないと、小さなダイアログが出るだけになってしまいます
JPanelのサイズに関わりなく、drawPage()内では用紙上の位置をポイント(1/72インチ)単位で指示しています。用紙は葉書なので位置の最大値はポイントで表した葉書の大きさになります。葉書は100mm×148mmなので、これをポイントに換算してJPanelのサイズとして指定すれば過不足なく全体を表示できることになります。直接これをsetPreferredSize()で指定しても良いのですが、今回はmainで印刷のためにMediaSizeから用紙サイズを求めているので、それを使うことにしました。計算では283.46×419.52になりますが、284×420に切り上げられているようです。画面ではどう頑張ってもピクセルですから、整数であることは理にかなっています。A4なら210.0mm×297.0mmで、596×842になります。
⑥ 印刷の前にダイアログで表示します
PrinterJobのインスタンスにPrintableなインスタンス(pable)を渡す前に、オプションダイアログを使って表示します。pableはJPanelを継承しているのでオプションダイアログを使って表示できます。
このダイアログはJOptionPaneクラスにいくつか用意されているstaticなメソッドの一つですが、選択肢を"印刷"、"中止"にして分かりやすくするためshowOptionDialog()にしました。印刷を選択すると、これまでどおり、PrinterJobのインスタンスに Printableなインスタンス(pable)を渡して印刷のダイアログに進みます。
拡大や縮小、スクロールなどはこれからの追加課題です。
