縦書き版の自動区切りプログラム
横書きの宛名の自動区切り「AfureString.java」の縦書き版です。これもクラス名は末尾にVerticalの意味でVを付けることにします。
/** 文字列を指定幅に改行しながら配置する-縦書き版 改行位置の候補文字列(文字列の末尾で改行)を配列で持つ。 候補文字が半角空白の場合はこれを印刷しない。 2019-12-01 @author Adachi Junichi @version 3.1 */ import java.awt.Graphics2D; import java.awt.FontMetrics; import java.awt.Font; public class AfureStringV { String zstr, str; float wdmm; float remm; int zslen; int nextjbgn; Graphics2D g2; FontMetrics fm; Font fnt; float mm2pt = 72/25.4f; float pt2mm = 25.4f/72; float ascent, mojih; float h2ascent = 0.9f; String[] afure = {" ","丁目","大字","字","郡","市","番地"}; boolean debug = false; /** string の内、areawidth に入る最初の部分をstrに保持。 */ public AfureStringV(Graphics2D g2, String string, float areawidth) { this(g2,string,areawidth,null,false); } /** string の内、areawidth に入る最初の部分をstrに保持。改行候補をafureにする */ public AfureStringV(Graphics2D g2, String string, float areawidth, String[] afure) { this(g2,string,areawidth,afure,false); } /** string の内、areawidth に入る最初の部分をstrに保持。debugをtrueにできる */ public AfureStringV(Graphics2D g2, String string, float areawidth, boolean debug){ this(g2,string,areawidth,null,debug); } /** string の内、areawidth に入る最初の部分をstrに保持。 改行候補をafureにし、debugをtrueにできる */ public AfureStringV(Graphics2D g2, String string, float areawidth, String[] afure, boolean debug){ this.g2 = g2; fm = g2.getFontMetrics(); fnt = fm.getFont(); zstr = (string!=null) ? string : ""; //nullなら"" wdmm = (areawidth>=0f) ? areawidth : 0f; //負なら0 if(afure!=null)this.afure=afure; this.debug = debug; mojih = fm.getFont().getSize(); ascent = mojih * h2ascent; zslen = zstr.length(); nextjbgn = 0; //string-index not cp-index if(debug) System.out.println("constr:'"+zstr+"',"+wdmm); nextLine(); } /** 次の行に書ける部分をstrに保持。 */ public void nextLine(){ //溢れ改行文字での改行はcpに踏み込まなくてもよい int jbgn = nextjbgn; str = zstr.substring(jbgn,zslen); int cpct = str.codePointCount(0,str.length()); remm = wdmm - mojih*pt2mm*cpct; int afi = 0; nextjbgn = -1; if(debug) System.out.printf("AfreBegn:%s %3d %3d %3.1f\n",str,jbgn,nextjbgn,remm); while(0>remm && afure.length>afi) { //① if (str.indexOf(afure[afi])>=0){ //② if (afure[afi].equals(" ") ){ //③ nextjbgn = str.indexOf(afure[afi])+jbgn; str = zstr.substring(jbgn,nextjbgn); nextjbgn++; }else{ //④ nextjbgn = str.indexOf(afure[afi])+jbgn+afure[afi].length(); str = zstr.substring(jbgn,nextjbgn); } cpct = str.codePointCount(0,str.length()); //⑤ remm = wdmm - mojih*pt2mm*cpct; } afi++; //⑥ } if(debug) System.out.printf("Afre2dec:%s %3d %3d %3.1f\n",str,jbgn,nextjbgn,remm); if (remm>=0) return; //⑦ cpct = str.codePointCount(0,str.length()); int maxcpct = (int)Math.floor(wdmm/(mojih*pt2mm)); if(maxcpct>cpct)maxcpct=cpct; remm = wdmm - mojih*pt2mm*maxcpct; int maxidx = str.offsetByCodePoints(0, maxcpct); str = str.substring(0,maxidx); nextjbgn = jbgn + maxidx; if (nextjbgn>=zslen) nextjbgn = -1; if(debug) System.out.printf("output--:%s %3d %3d %3.1f\n",str,jbgn,nextjbgn,remm); } /** 書くべき文字が残っている時trueを返す */ public boolean hasNext(){ return nextjbgn>=0; } /**縦書きを左寄せのように上に詰める。hmを中心線にする*/ //str,fm,g2,mm2pt,mojih,ascent public void drawTop(float hm, float vm) { int strlen = str.length(); int i=0; int cpcti = 0; int nexti; float fontwd; while (strlen>i){ nexti = str.offsetByCodePoints(i,1); fontwd = fm.stringWidth(str.substring(i,nexti)); g2.drawString(str.substring(i,nexti), hm*mm2pt-fontwd/2, vm*mm2pt+mojih*cpcti+ascent); i=nexti; cpcti++; } } /**縦書きを右寄せのように下に詰める。hmを中心線にする*/ //str,fm,g2,mm2pt,mojih,ascent public void drawBottom(float hm, float vm) { int strlen = str.length(); int i=0; int cpcti = 0; int nexti; float fontwd; while (strlen>i){ nexti = str.offsetByCodePoints(i,1); fontwd = fm.stringWidth(str.substring(i,nexti)); g2.drawString(str.substring(i,nexti), hm*mm2pt-fontwd/2, (vm+remm)*mm2pt+mojih*cpcti+ascent); i=nexti; cpcti++; } } /**縦の均等割付 hmを中心線にする */ //str,fm,g2,mm2pt,mojih,ascent,gapmm public void drawTtoB(float hm, float vm) { int strlen = str.length(); int cpct = str.codePointCount(0,strlen); if (cpct>1){ int i=0; int cpcti = 0; int nexti; float fontwd; float gapmm = remm/(cpct-1); while (strlen>i){ nexti = str.offsetByCodePoints(i,1); fontwd = fm.stringWidth(str.substring(i,nexti)); g2.drawString(str.substring(i,nexti), hm*mm2pt-fontwd/2, (vm+gapmm*cpcti)*mm2pt+mojih*cpcti+ascent); i=nexti; cpcti++; } }else if(cpct==1){ g2.drawString(str,hm*mm2pt-fm.stringWidth(str)/2, (vm+remm/2)*mm2pt+ascent); } } /**縦の中央寄せ hmを中心線にする */ //str,fm,g2,mm2pt,mojih,ascent public void drawMid(float hm, float vm) { int strlen = str.length(); int i=0; int cpcti = 0; int nexti; float fontwd; while (strlen>i){ nexti = str.offsetByCodePoints(i,1); fontwd = fm.stringWidth(str.substring(i,nexti)); g2.drawString(str.substring(i,nexti), hm*mm2pt-fontwd/2, (vm+remm/2)*mm2pt+mojih*cpcti+ascent); i=nexti; cpcti++; } } }
自動区切りについて若干の説明
区切り候補文字列による自動区切りの部分についてプログラム解説をしておきます。丸付き数字はプログラム中の部分を指します。
① 区切り候補を変えて繰り返し
区切り候補を順番に試してうまく入るまで繰り返します。候補がなくなったら諦めます(whileを抜けるとAdjustStringと同様な区切り動作に移ります)。
② 候補が文字列の中にあれば、
候補までで区切った文字列を用意します。
③ 区切り候補がスペースの場合
区切り候補がスペースの場合はスペースの前までが「候補までで区切った文字列」になります。「区切った文字列」の次の文字はスペース(区切り候補)の次の文字になります。nextjbgnはStringのindexですが、サロゲートペアの考慮は自動的にできています。
④ 区切り候補がスペースでない場合
区切り候補がスペースでない場合はその後ろまでが「候補までで区切った文字列」になります。区切り候補の長さ分を加えて、文字列を作り、次の文字のindexを決めます。これも、サロゲートペアの考慮は自動的にできています。
⑤ 文字列が指定範囲に入るか
候補までで区切った文字列が何文字かをコードポイント数から求めて、文字の高さを掛け、指定の高さから引きます。マイナスになれば入らないということになります。
⑥ 候補番号を進めます
ここまでがwhileです。この時点でremmがマイナスで、区切り候補がまだ残っていれば繰り返します。
⑦ remmがマイナスでなければnextLine()を終了
remmがマイナスでないなら「候補までで区切った文字列」が指定範囲に入るということなので、戻ります。入らなければAdjustStringと同様な区切り動作に移ります。
この条件文は横書きのAfureStringでは不要でしたが、縦書きでは計算方法が異なるので必要です。
AdjustStringVとAfureStringVとはやっていることが似ていますから、統合してしまうこともありかもしれません。ただ、別れたままのほうが理解しやすいので、プログラムの修正や機能追加がしやすいとも言えます。
AdjustStringVは文字列中の処理位置をコードポイント単位で扱っています。これは書ききれなかった部分の開始位置を呼び出し側に返す必要から来ています。サロゲートペアであっても人間には1文字に見えるということからコードポイントの数である必要があります。開始位置を返すことで2行目は異なる幅に配置するなどAfureStringVに比べて細かな制御ができます。
AfureStringVは文字列中の処理位置をString(その内実はchar配列)のindexで扱います。これは改行候補文字での改行ではサロゲートペアを考慮する必要がないからです。また、2行目以降の継続処理も自動に任せるために、コードポイントの数に直す必要がないこともあります。
縦書き版の自動区切りプログラムの動作確認
BufferedImageに書いて表示だけします。印刷はしません。「AfureStringV.java」の使用方法を示すものです。ここでもサロゲートペアを使用する文字をいくつか入れましたが、Shift_JIS環境でもコンパイルできるようにしてあります。
import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.RenderingHints; import java.awt.image.BufferedImage; import javax.swing.*; public class TestAfureStringV { public static void main( String[] args ) { BufferedImage buffimg = new BufferedImage(420,420,BufferedImage.TYPE_INT_RGB); Graphics2D g = buffimg.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setColor(Color.WHITE); g.fillRect(0,0,buffimg.getWidth(),buffimg.getHeight()); g.setColor(Color.BLACK); g.setFont(new Font(Font.SERIF, Font.PLAIN, 24)); JLabel picLabel = new JLabel(new ImageIcon(buffimg)); String str1 = "青森県弘前市大字糀ヶ淵四丁目13番地1 県営丈`土`住宅"; //注2 AfureStringV af = new AfureStringV(g, str1, 80f); float ym = 10f; float x = 130f; float xpch = -12f; af.drawTtoB(x,ym); while(af.hasNext()){ af.nextLine(); x += xpch; af.drawBottom(x,ym); } g.setColor(Color.BLUE); x = 75f; str1 = "定められた改行候補がなければ入りきる最大数で改行します"; af = new AfureStringV(g, str1, 81f); af.drawTop(x,ym); while(af.hasNext()){ af.nextLine(); x += xpch; af.drawBottom(x,ym); } g.setColor(Color.RED); x = 30f; af = new AfureStringV(g, "その他のメソッド",80f); af.drawTtoB(x,ym); af.drawMid(x+xpch,ym); System.out.printf("JDialog!"); String[] goOrNot = {"印刷","中止"}; int rev = JOptionPane.showConfirmDialog( null,picLabel, "AfreStringV動作確認", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE ); } }
注2
繰り返しになりますが、U+29e3dの文字は魚偏に花で「ホッケ」という文字、U+2000Bは丈の右上に犬のように点のある文字。U+2123Dは土の右上に点のある文字でのことです。
複数の宛名プログラム本体(縦書き)
横書きの「PostcardsJushoHor.java」と考え方は同じです。drawPage()メソッド部分だけが異なります。このプログラムは後で改良されて再度掲載されます。
/** PostcardsJushoVer.java PostcardsJushoHor.javaの縦書き版 宛名を縦書きにし、1行に入り切らない時は複数行に分けます。 プレビューはMultiPageDialogを適用 枠に合わせて描画も拡大・縮小、JScrollPaneも使用します。 AddressListを使って複数の宛名を扱います。 2019-12-02 @author Adachi Junichi @version 3.0 */ import java.awt.*; import java.awt.print.*; import javax.print.*; import javax.print.attribute.*; import javax.print.attribute.standard.*; import javax.swing.*; import java.util.List; public class PostcardsJushoVer extends JPanel implements Printable,MultiPageViewable { List<Person> hitos; int previewpage = 0; double wZh; Dimension motodims = new Dimension(); Dimension scaledims = new Dimension(); float mm2pt = 72/25.4f; double trdx = 0; //spinner-bidou double trdy = 0; //spinner-bidou PrintRequestAttributeSet rqset ; double ratio = 1.0d; //用紙サイズに応じてsetPanelSize()で再設定 public PostcardsJushoVer(List<Person> hitos){ this.hitos = hitos; setBackground(Color.white); 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)); setPanelSize(mwidth,mheight); } /**プレビューのサイズ、拡大率を設定する*/ public void setPanelSize(float w,float h){ motodims.setSize(w*mm2pt,h*mm2pt); setPreferredSize(motodims); wZh = motodims.getWidth()/motodims.getHeight(); ratio = 2.0d; //葉書は小さいので2倍で表示 } @Override public double getRatio(){ return ratio; } @Override public int incPreviewPage(){ previewpage++; if (previewpage>=hitos.size()) previewpage=0; repaint(); return previewpage; } @Override public int decPreviewPage(){ previewpage--; if (0>previewpage) previewpage = hitos.size()-1; repaint(); return previewpage; } @Override public int number(){ return hitos.size(); } @Override public String getStatus(){ String retv; if(hitos.size()==0){ retv ="...."; } else{ retv = (previewpage+1)+"/"+hitos.size(); } return retv; } @Override public String getCurrentData(){ String retv; Person hito = hitos.get(previewpage); retv = hito.zip+" "+hito.addr +"/"+ hito.name; return retv; } //spinner-bidou @Override public void setTrdx(double x){ trdx = x*mm2pt; //System.out.println("trdx:"+trdx);//test } //spinner-bidou @Override public void setTrdy(double y){ trdy = -y*mm2pt; } @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; double scale; if((viewpw-vbarw)/(double)viewph > wZh){ scaledims.setSize(viewpw-vbarw, (viewpw-vbarw)/wZh); scale = (viewpw-vbarw)/motodims.getWidth(); }else if( wZh > (double)viewpw / (viewph-hbarh) ){ scaledims.setSize((viewph-hbarh)*wZh, viewph-hbarh); scale = (viewph-hbarh)/motodims.getHeight(); }else{ if ( (double)viewpw/viewph > wZh ) { scaledims.setSize(viewph*wZh, viewph); scale = viewph/motodims.getHeight(); }else{ scaledims.setSize(viewpw, viewpw/wZh); scale = viewpw/motodims.getWidth(); } } g2.scale(scale,scale); setPreferredSize(scaledims); drawPage(g2,previewpage); } @Override public int print(Graphics g, PageFormat pf, int pageIndex) { if (pageIndex >= hitos.size()) return NO_SUCH_PAGE; Graphics2D g2 = (Graphics2D) g; drawPage(g2,pageIndex); return PAGE_EXISTS; } public void drawPage(Graphics2D g2,int idx){ g2.translate(trdx,trdy); //Graphics2D g2 = (Graphics2D)g; String zip = hitos.get(idx).zip; String addr = hitos.get(idx).addr; String name = hitos.get(idx).name; g2.setColor(Color.BLACK); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); 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 yaddr = 25f; float haddr = 94f; float xaddr = 87f; float xpch = -7.5f; float yname = 32f; float xname = 53f; float hname = 87f; g2.setFont(new Font("Serif", Font.PLAIN, 16)); AfureStringV afv; afv = new AfureStringV(g2,addr,haddr); afv.drawTop(xaddr, yaddr); int ct = 0; while(afv.hasNext()){ afv.nextLine(); afv.drawBottom(xaddr + xpch*(++ct), yaddr); } g2.setFont(new Font("Serif", Font.PLAIN, 18)); AdjustStringV adv; adv = new AdjustStringV(g2,name+"様",hname); adv.drawTtoB(xname, yname); } public void doPrint() { PrinterJob pj = PrinterJob.getPrinterJob(); pj.setPrintable(this); if (pj.printDialog(rqset)) { try { pj.print(rqset); } catch (PrinterException e) { System.err.println(e); } } } public static void main(String[] args) { javax.swing.SwingUtilities.invokeLater(new Runnable(){ public void run() { createAndShowGUI(); } }); } public static void createAndShowGUI() { List<Person> ls = AddressList.readTextFile("kakuujusho.txt"); PostcardsJushoVer pable = new PostcardsJushoVer(ls); //xxxx MultiPageDialog dialog = new MultiPageDialog(null,true,pable); dialog.pack(); dialog.showDialog(); while (dialog.getValue()==1){ System.out.println("印刷を開始"); pable.doPrint(); System.out.println("印刷を終了"); dialog.showDialog(); } System.out.println("Canseled:"+dialog.getValue()); dialog.dispose(); } }
このプログラムのコンパイルと実行には、これまでに紹介した次のプログラムが同一フォルダに必要です。
MultiPageViewable.java AddressList.java AdjustString.java AdjustStringV.java AfureStringV.java
PostcardsJushoVer.java実行結果
ハイフォンや長音符への対処はこの後でしますが、取りあえず住所の候補による自動区切りはうまく働きます。ページを切り替えて確認できます。
数字はこのままでもいいのですが、ハイフォンと数字の1が区別しにくいのが気がかりです。