SHOEISHA iD

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

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

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

縦書きのための均等割付と宛名自動改行印刷

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

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

縦書き版の自動区切りプログラム

 横書きの宛名の自動区切り「AfureString.java」の縦書き版です。これもクラス名は末尾にVerticalの意味でVを付けることにします。

AfureStringV.java
/**
文字列を指定幅に改行しながら配置する-縦書き版
改行位置の候補文字列(文字列の末尾で改行)を配列で持つ。
候補文字が半角空白の場合はこれを印刷しない。
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環境でもコンパイルできるようにしてあります。

TestAfureStringV.java
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は土の右上に点のある文字でのことです。

TestAfureStringV.javaの実行結果
TestAfureStringV.javaの実行結果

複数の宛名プログラム本体(縦書き)

 横書きの「PostcardsJushoHor.java」と考え方は同じです。drawPage()メソッド部分だけが異なります。このプログラムは後で改良されて再度掲載されます。

PostcardsJushoVer.java(ハイフォンなど未対策版)
/** 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実行結果

 ハイフォンや長音符への対処はこの後でしますが、取りあえず住所の候補による自動区切りはうまく働きます。ページを切り替えて確認できます。

PostcardsJushoVer.javaの実行結果
PostcardsJushoVer.javaの実行結果

 数字はこのままでもいいのですが、ハイフォンと数字の1が区別しにくいのが気がかりです。

次のページ
縦書きのための文字の変換を考える

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

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

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

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

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング