SHOEISHA iD

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

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

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

宛名印刷と印刷プレビュー

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

  • X ポスト
  • このエントリーをはてなブックマークに追加

住所の区切りを自動化する

 上記の印字例の住所部分を下図の現状に示しました。AdjustStringクラスでの改行は見やすくありません。"相模原市"の後と、"三上ハイツ"の前で区切りたいところです。

改行候補で区切る
改行候補で区切る

 この場合は"市"のあとで区切る、" "(スペース)の後で区切るなどの処理をすればよいと考えられます。この他にも"県"とか"丁目"、"番地"などいろいろ候補は挙げられますが、"市"というのはうまくいかない場合があります。市原市や四日市市、市浦村(青森県)などの問題です。このため改行候補は複数にして優先順位を印刷するデータに合わせて変更するなどの対処が必要なことも考えられます。

 また、全角半角のスペースは特別な存在です。ここで改行することになった場合は印字しなくて良い文字だからです。

住所の自動区切りプログラム

 前項の方針でAdjustStringのようなプログラムをつくります。まずプログラムリストを示し、使い方を説明し、その後で動作原理を説明します。

AfureString.java
/**
文字列を指定幅に改行しながら配置する
改行位置の候補文字列(文字列の末尾で改行)を配列で持つ。
候補文字が全・半角空白の場合はこれを印刷しない。
2019-11-18
@author Adachi Junichi
@version 3.1
*/
import java.awt.Graphics2D;
import java.awt.FontMetrics;

public class AfureString {
    String zstr, str;
    int    nextjbgn;
    int    zslen;
    float  wdmm;
    float  remm  = 0f;
    Graphics2D  g2;
    FontMetrics fm;
    float mm2pt = 72/25.4f;
    float pt2mm = 25.4f/72;
    String[] afure = {" ","丁目","大字","字","郡","市","番地"};
    boolean debug = true;
    /** string の内、areawidth に入る最初の部分をstrに保持。 */
    public AfureString(Graphics2D g2, String string, float areawidth) {
        this(g2,string,areawidth,null,false);
    }
    /** string の内、areawidth に入る最初の部分をstrに保持。改行候補をafureにする */
    public AfureString(Graphics2D g2, String string, float areawidth, String[] afure) {
        this(g2,string,areawidth,afure,false);
    }
    /** string の内、areawidth に入る最初の部分をstrに保持。debugをtrueにできる */
    public AfureString(Graphics2D g2, String string, float areawidth, boolean debug){
        this(g2,string,areawidth,null,debug);
    }
    /** string の内、areawidth に入る最初の部分をstrに保持。改行候補をafureにし、debugをtrueにできる */
    public AfureString(Graphics2D g2, String string, float areawidth, String[] afure, boolean debug){
        if(afure!=null)this.afure=afure;
        this.debug = debug;
        this.g2 = g2;
        fm = g2.getFontMetrics();
        zstr = (string!=null) ? string : "";  //nullなら""
        wdmm = (areawidth>=0f) ? areawidth : 0f;  //負なら0
        zslen = zstr.length();
        nextjbgn = 0;
        if(debug) System.out.println("constr:'"+zstr+"',"+wdmm);
        nextLine();
    }
    /** 次の行に書ける部分をstrに保持。 */
    public void nextLine(){
        //溢れ改行文字での改行はcpに踏み込まなくてもよい
        int jbgn = nextjbgn;
        str = zstr.substring(jbgn,zslen);
        remm = wdmm - fm.stringWidth(str)*pt2mm;
        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);
                }
                remm = wdmm - fm.stringWidth(str)*pt2mm;
            }
            afi++;
        }
        if(debug) System.out.printf("Afre2dec:%s %3d %3d  %3.1f\n",str,jbgn,nextjbgn,remm);
        while(0>remm && !str.isEmpty()) {
            int newendidx = str.offsetByCodePoints(str.length(), -1); //一つ前のindexを求める
            str = str.substring(0,newendidx);
            remm = wdmm - fm.stringWidth(str)*pt2mm;
            nextjbgn = jbgn + str.length();
        }
        if(debug) System.out.printf("output--:%s %3d %3d  %3.1f\n",str,jbgn,nextjbgn,remm);
    }
    /** 書くべき文字が残っている時trueを返す */
    public boolean hasNext(){
        return nextjbgn>=0;
    }
    public void drawLeft(float x, float y) {
        g2.drawString(str,x*mm2pt,y*mm2pt);
    }
    public void drawRight(float x, float y) {
        g2.drawString(str,(x+remm)*mm2pt,y*mm2pt);
    }
    public void drawCenter(float x, float y) {
        g2.drawString(str,(x+remm/2)*mm2pt,y*mm2pt);
    }
    public void drawKintou(float x, float y) {
        float hpp = 0;
        int strlen = str.length();
        int cpct = str.codePointCount(0,strlen);
        if (cpct!=1){
            float aidamm = remm/(cpct-1);
            int i=0;
            int nexti = 0;
            int ct = 0;
            while (strlen>i){ 
                nexti = str.offsetByCodePoints(i,1);
                g2.drawString(str.substring(i,nexti),(x+aidamm*ct)*mm2pt+hpp,y*mm2pt);
                hpp = fm.stringWidth(str.substring(0,nexti));
                i=nexti;
                ct++;
            }
        }else{
            drawCenter( x, y);
        }
    }
}

AfureString.javaの使い方

 上に記した「PrintableWithPreview01.java」を改変して、住所部分をAdjustStringでなくてAfureStringを使うように書き換えます。

AfureString.javaの使い方
AfureString afs;
afs = new AfureString(g2,addr,waddr);
afs.drawLeft(xaddr, yaddr);
int ct = 0;
while(afs.hasNext()){
    afs.nextLine();
    afs.drawRight(xaddr, yaddr + ypch*(++ct));
}

 最初のインスタンスの生成で1行目をセットしておいて、drawLeft()で左寄せに印字し、その後hasNext()である間、nextLine()で次の行の文字列をセットさせ、drawRight()で右寄せに印字します。

 AdjustStringに比べ、文字列をどこまで書いたかの情報はインスタンス内に持つので、開始位置を指定する必要はありません。

 改行候補は、指定しなければデフォルトの候補を使用します。必要に応じてコンストラクタに引数として与えて一時的に変更できます。改行候補はインスタンスの生成時に一度使われることに注意してください。

 改行候補は文字列の配列です。デフォルトでは次のように設定されています。

String[] afure = {" ","丁目","大字","字","市","番地"}

 配列の格納順に改行候補になります。この項目と順番は扱う地域の特性により変えたほうが良いかもしれません。"市"が下位にあるのは、"市原市"や"市浦村"など、地域名に"市"が入っている場合に不都合だからです。同じ理由で"町"や"村"は除外してあります。

 改行候補は文字列を許しますから、"市原市"がよく出てくるデータを扱う場合にはあえて、"市原市"を上位の候補に加え、その後に"市"を配置するという手もありです。

 改行候補でうまく切れない場合は、AdjustStringのように入るところまでという所で切るようになっています。もしどうしてもうまくいかないなら、住所データ内に" "を挿入して調整する手もあります。

 AfureString.javaを使うように変更した「PrintableWithPreview01.java」のプレビューの様子です。

住所の自動改行
住所の自動改行

AfureStringの仕組み

 改行候補で区切って指定幅に入りきる最初の文字列をstrに入れ、次の行に書く文字列の開始位置(元の文字列で)をnextjbgnに入れるという過程の解説です。

 サロゲートペアが含まれていても(それが改行候補内であっても)問題なく動きます。

AfureString.javaの核心部分
public void nextLine(){
    //改行候補文字での改行はcpに踏み込まなくてもよい
    int jbgn = nextjbgn;
    str = zstr.substring(jbgn,zslen);
    remm = wdmm - fm.stringWidth(str)*pt2mm;
    int afi = 0;
    nextjbgn = -1;
    if(debug) System.out.printf("Afre:%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);
            }
            remm = wdmm - fm.stringWidth(str)*pt2mm;
        }
        afi++;
    }
...この後、remmがまだマイナスならAdjustStringの手法で一文字ずつ減らしていく。

 上の実行例での計算過程を図示すると次のようになります。

 最初の候補" "で区切っても入り切りません。次の"丁目","大字","字",は住所に含まれませんからスキップします。"市"で区切ると入ります。これが1回目。次は" "で区切ると入ります。これが2回目です。最後は区切らなくても入ります。これが3回目です。

(クリックすると拡大表示します)
(クリックすると拡大表示します)
  • jはjbgnの値。これは元の文字列zstr内のindexの値
  • iはstr.indexOf()の値。zstrから切り出したstr内のindexであることに注意
  • nはnextjbgnの値。次の行を求める時のzstr内の開始位置。次の呼び出しの最初でjbgnに代入される。候補が全角半角のスペースでなければ、「jbgn+i+候補の長さ」と計算される

次のページ
プレビューのための独自ダイアログを作る

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

  • X ポスト
  • このエントリーをはてなブックマークに追加
Javaの標準機能だけで実現する帳票印刷連載記事一覧

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/11894 2020/07/08 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング