SHOEISHA iD

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

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

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

均等割付・右寄せ・センタリングを簡単に扱う道具を作る

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

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

入りきらない時は小さい字にする

 2行にすると高さが不足するときには、小さなフォントにすることもできます。

 次のプログラムは、「PrintableWithAdjustString2.java」の2つめの文字列を書く部分の変更です。1ポイントずつフォントを小さくしていき、文字列が入りればその大きさで印字します。あまりに小さいとバランスが悪いのである程度小さくなったら2行にすることを考えると良いかもしれません。ここでは元のフォントの半分になったら2行にするようにしています。

PrintableWithAdjustString2.javaの変更部分
        ads =  new AdjustString(g2,str2,w-padx*2);
        if(ads.hasNext()){
            Font  f1 =  g2.getFontMetrics().getFont();
            float fmin = f1.getSize2D()/2;
            int   f1styl = f1.getStyle();
            float fsize = f1.getSize2D();
            while(ads.hasNext() && fsize > fmin){
                g2.setFont(f1.deriveFont(f1styl,--fsize));
                ads = new AdjustString(g2,str2,w-padx*2);
            }
            if(ads.hasNext()){
                ads.drawLeft(x+padx,y+2*h-pady-fsize/mm2pt);
                ads = new AdjustString(g2, str2 ,w-padx*2, ads.getNextPt());
                ads.drawRight(x+padx,y+2*h-pady);
            }else{
                ads.drawKintou(x+padx,y+2*h-pady);
            }
            g2.setFont(f1);
        }

 実行結果です。1行に収まっています。

指定範囲が狭くてフォントを小さくする場合
指定範囲が狭くてフォントを小さくする場合

 さらに文字数が多くて、フォントが半分になっても入りきらない場合は2行にします。半分なので、高さに余裕がなくても入るだろうという考え方です。

フォントを半分にしても入らない場合は2行にする
フォントを半分にしても入らない場合は2行にする

罫線を引く

 第1回で直線と四角形の描き方を紹介しました。座標の単位はポイントで指定しますが、意外と面倒です。文字列と同様に描画位置をmm単位で指定するようにします。

 まず直線について簡単に復習します。

 直線を表現するクラスLine2DのコンストラクタはネストされたLine2D.DoubleまたはLine2D.Floatを選べますが、文字の位置指定に合わせてfloatにします。

Line2D.Float(float x1, float y1, float x2, float y2)

 端点の座標を後から設定したり変更したりすることも可能です。

Line2D line = new Line2D.Float();
line.setLine(float x1, float y1, float x2, float y2);

Linemmというクラスを作ります。

 帳票印刷では左右の位置が同じ横線を高さを変えて何本も引くことがよくあります。縦線も同様です。横先3本、縦線2本で表を作るときに、次のように指定できれば便利です。

Linemm line = new Linemm();
line.setLR(x1, x2);
line.setY(y1);
g2.draw(line);
line.setY(y2);
g2.draw(line);
line.setY(y3);
g2.draw(line);
line.setTB(y1, y2);
line.setX(x1);
g2.draw(line);
line.setX(x2);
g2.draw(line);

 java.awt.geom.Line2D.Floatを継承したクラスです。mm単位で位置を指定できるようにし、 水平線なら左右の位置を先に指定して、yの位置を変えながら複数描画する時に便利なメソッドを加えました。 鉛直線の場合は上下の位置を先に指定しておいて、xの位置を変えながら描画します。

Linemm.java
/** 
2019-9-20
@author Adachi
@version 3.0
*/
import java.awt.geom.Line2D;

/** Line2D.floatを継承し引数の単位をmmにする */
public class Linemm extends Line2D.Float {
    float x1, y1, x2, y2;
    static final float mm2pt = 72/25.4f;
    static final float pt2mm = 25.4f/72;
    /** (0,0)-(0,0)に初期化された直線を作る */
    public Linemm(){
        super();
    }
    /** mm単位で指定された座標(x1mm,y1mm)-(x2mm,y2mm)に初期化された直線を作る */
    public Linemm(float x1mm, float y1mm, float x2mm, float y2mm){
        super();
        x1 = x1mm * mm2pt;
        y1 = y1mm * mm2pt;
        x2 = x2mm * mm2pt;
        y2 = y2mm * mm2pt;
        setLine( x1, y1, x2, y2); 
    }
    /** 線の2つの端点のx座標をそれぞれ指定します。yは変化させません。
        このメソッドは水平線を引くことを想定し、2つの端点は左右端です。*/
    public void setLR( float x1mm, float x2mm ) {
        x1 = x1mm * mm2pt;
        x2 = x2mm * mm2pt;
        setLine( x1, y1, x2, y2); 
    }
    /** 線の2つの端点のy座標を2つとも同じ値に指定します。xは変化させません。
        このメソッドは水平線を引くことを想定しています。*/
    public void setX(float xmm) {
        x1 = xmm * mm2pt;
        x2 = x1;
        setLine( x1, y1, x2, y2); 
    }
    /** 線の2つの端点のy座標をそれぞれ指定します。xは変化させません。
        このメソッドは縦線を引くことを想定しています。2つの端点はTopとBottomです。*/
    public void setTB( float y1mm, float y2mm ) {
        y1 = y1mm * mm2pt;
        y2 = y2mm * mm2pt;
        setLine( x1, y1, x2, y2); 
    }
    /** 線の2つの端点のx座標を2つとも同じ値に指定します。yは変化させません。
        このメソッドは縦線を引くことを想定しています。*/
    public void setY(float ymm) {
        y1 = ymm * mm2pt;
        y2 = y1;
        setLine( x1, y1, x2, y2); 
    }
    /** 線の2つの端点の座標をそれぞれ指定します。斜めの線を引くことを想定しています。
        setLR()とsetTB()をく組み合わせても同じことができます。*/
    public void setPP( float x1mm,float y1mm, float x2mm, float y2mm ) {
        x1 = x1mm * mm2pt;
        x2 = x2mm * mm2pt;
        y1 = y1mm * mm2pt;
        y2 = y2mm * mm2pt;
        setLine( x1, y1, x2, y2); 
    }
}

均等割付けと罫線の使用例-通知表印刷

 均等割付けと罫線のクラスを組み合わせての使用例を示します。題材は通知表です。まだ縦書きの均等割付けを作っていないし、プレビューもできていないので、途中までにしておきます。

 このプログラムには上記の「AdjustString.java」と「Linemm.java」が必要です。

Tsuuchi.java
/** 
@author Adachi
@version 3.0
2019-9-20
*/
import java.awt.*;
import java.awt.print.*;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.standard.MediaSize;
import javax.print.attribute.standard.MediaSizeName;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.MediaPrintableArea;

public class Tsuuchi implements Printable {
    String[] kams;
    int[] tans, tens;
    public Tsuuchi(String[] kams, int[] tans, int[] tens){
        this.kams = kams;
        this.tans = tans;
        this.tens = tens;
    }
    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) {
        if (pageIndex != 0) return NO_SUCH_PAGE;
        Graphics2D g2 = (Graphics2D)g;
        float hbas = 20.0f;  //左端の位置
        float hwkm = 27.5f;  //科目名の幅
        float hwtn = 7f;     //単位数の幅
        float hptch = 45f/4;  //学期などの幅
        float vthtop = 41.0f; //ヘッダ行の上の位置
        float vtdtop = 64.0f; //行データの上の位置
        float vptch  = 9.8f;  //行データの各行の高さ
        float mm2pt  = 72/25.4f;
        float pt2mm  = 25.4f/72;
        
        float hwall = hwkm+hwtn+hptch*4;
        BasicStroke boldstroke = new BasicStroke(1.0f);
        BasicStroke medmstroke = new BasicStroke(0.7f);
        BasicStroke thinstroke = new BasicStroke(0.0f);
        g2.setStroke(boldstroke);
        Linemm line = new Linemm();
        line.setTB(vthtop, vtdtop+vptch*kams.length);
        line.setX(hbas);
        g2.draw(line);
        line.setX(hbas+hwall);
        g2.draw(line);
        line.setLR(hbas, hbas+hwall);
        line.setY(vthtop);
        g2.draw(line);
        line.setY(vtdtop+vptch*kams.length);
        g2.draw(line);
        g2.setStroke(medmstroke);
        line.setY(vtdtop);
        g2.draw(line);

        g2.setStroke(thinstroke);
        line.setLR(hbas, hbas+hwall);
        for (int k=1; kams.length>k; k++){ //1 not 0
            line.setY(vtdtop+vptch*k);
            g2.draw(line);
        }
        line.setTB(vthtop, vtdtop+vptch*kams.length);
        line.setX(hbas+hwkm);
        g2.draw(line);
        for (int i=0; 4>i; i++){
            line.setX(hbas+hwkm+hwtn+hptch*i);
            g2.draw(line);
        }
        line.setPP(hbas, vthtop, hbas+hwkm, vtdtop); //斜線
        g2.draw(line);

        Font font10 = new Font("Serif", Font.PLAIN, 10);
        g2.setFont(font10);
        float padbtm = (vptch-g2.getFontMetrics().getHeight()*pt2mm)/2;
        float vm,hm;
        float padx = 2f;
        AdjustString ads;
        for (int k=0; kams.length>k; k++){
            vm = vtdtop+vptch*(k+1)-padbtm;
            ads = new AdjustString(g2, kams[k] ,hwkm-padx*2f);
            if(ads.hasNext()){  //長い科目名を2行に
                ads.drawLeft(hbas+padx,vm+padbtm*3/4-vptch/2);
                ads = new AdjustString(g2, kams[k] ,hwkm-padx*2f, ads.getNextPt());
                ads.drawRight(hbas+padx,vm+padbtm/2);
            }else{
                ads.drawKintou(hbas+padx,vm);
            }            
            ads = new AdjustString(g2, String.valueOf(tans[k]), hwtn);//単位数
            ads.drawCenter(hbas+hwkm,vm);
            ads = new AdjustString(g2, String.format("%3d",tens[k]), hptch);//評点1
            hm = hbas+hwkm+hwtn;
            ads.drawCenter(hm,vm);
        }
        return PAGE_EXISTS;
    }

    public static void main(String[] args) {
        String[] kams = { "国語総合","現代社会","数学Ⅰ","数学A","化学基礎",
                              "生物基礎","体育","保健","音楽Ⅰ","コミュニケーション英語Ⅰ",
                              "英語表現Ⅰ","家庭基礎","情報の科学" };
        int[] tans = { 6,3,4,2,2,
                           2,2,1,1,2,4,
                           2,2,2 };
        int[] tens = { 55,45,65,45,75,
                           45,55,55,60,45,
                           65,65,55 };

        Printable pable = new Tsuuchi(kams,tans,tens);
        PrintRequestAttributeSet rqset = new HashPrintRequestAttributeSet();
        MediaSizeName msname = MediaSizeName.ISO_A4; //A4用紙
        rqset.add(msname);
        MediaSize msize = MediaSize.getMediaSizeForName(msname);
        float mwidth  = msize.getX(MediaPrintableArea.MM);
        float mheight = msize.getY(MediaPrintableArea.MM);
        float L = 10.1f;
        float R = 10.2f;
        float T = 10.3f;
        float B = 10.4f;
        rqset.add(new MediaPrintableArea(
            L, T, (mwidth - L - R), (mheight - T - B),
            MediaPrintableArea.MM));
        rqset.add(OrientationRequested.PORTRAIT);
        PrinterJob pj = PrinterJob.getPrinterJob();
        pj.setPrintable(pable);
        if (pj.printDialog(rqset)) {
            try { pj.print(rqset); }
            catch (PrinterException e) {
                System.err.println(e);
            }
        }
    }
}

通知表印刷の印刷結果

 A4用紙の左上部分です。今後出欠部分や所見欄を加えていきます。

通知表の途中まで
通知表の途中まで

まとめとプログラム一覧

今回のまとめ

 指定範囲に文字列を均等割付、右寄せ、左寄せ、センタリングなどの位置調整をして書くためのクラスとメソッドを二段階で作っていきました。入り切らない場合は、入るところまで書いたり、2行に分けて書いたり、文字を小さくして書いたりできます。また面倒な直線の位置指定を帳票設計に使いやすいように仲介するクラスも作成しました。

 この2つを使って作る罫線付きの帳票の例として、通知表の最初の部分を紹介しました。

 この回で掲載したプログラムリストの一覧を示します。強調しているのは、今後の連載で部品として使う可能性のあるものや改良・追加・発展が考えられるプログラムです。

プログラム一覧

  • AdjustStringOvf.java:
    指定範囲に文字列を均等割付、右寄せ、左寄せ、センタリングなどの調整をして描画するプログラム。指定範囲に入りきらない場合ははみ出す。
  • PrintableWithAdjustStringOvf.java:
    AdjustStringOvf.javaを使って帳票を印刷する例
  • AdjustString.java
    指定範囲に文字列を均等割付、右寄せ、左寄せ、センタリングなどの調整をして描画するプログラム。指定範囲に入るところまで書く。
  • PrintableWithAdjustString2.java:
    AdjustString.javaを使い、均等割付印刷をします。 1行に入りきらない時は2行に分けます。
  • Linemm.java
    java.awt.geom.Line2D.Floatを継承するクラス。mm単位で位置を指定できるようにし、指定方法も変える。
  • Tsuuchi.java
    AdjustString.java、Linemm.javaを使った通知表印刷の例(途中まで)。

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

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

もっと読む

この記事の著者

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

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

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

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

この記事をシェア

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

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング