javaで帳票印刷 禁則処理

句読点のみの禁則

句読点が行頭に来るときにはその前の一文字も一緒に次の行に持ってきます。つまり追い出し禁則とします。

例えばこんな感じ

行頭に句読点が来る場合には
、一つ前の文字から次の行に
する。

 ⇩

行頭に句読点が来る場合に
は、一つ前の文字から次の行
にする。

もともと前の行は均等割付けをしているので、一文字減った分はすべての文字間に分散されて目立ちません。

実際の例です

2行目に入る予定だった"す"が3行目に行き、2行目が1文字減っているのがわかります。

問題はどこまでやるかです。LibreOfficeに標準で登録されている禁則文字は次の通り

行頭禁則文字
!%),.:;?]}¢°’”‰′″℃、。々〉》」』】〕ぁぃぅぇぉっゃゅょゎ゛゜ゝゞァィゥェォッャュョヮヵヶ・ーヽヾ!%),.:;?]}。」、・ァィゥェォャュョッー゙゚¢
行末禁則文字
$([¥{£¥‘“〈《「『【〔$([{「£¥

行末は我慢することにします。

行頭も全部を取り扱うと2文字続いて出現する可能性があって面倒なので、一文字限りの処理とします。

行頭禁則文字は",.、。"の4つとします。アルゴリズムとしては次の行の先頭文字が行頭禁則文字群に含まれていたら追い出し禁則を行うというやり方ですので、扱う文字を変更することはプログラムレベルでは簡単ですが、ユーザーが変更できるようにはしません。

方針

本来自然なのは、AjustStringのインスタンスの生成の時に、残りの文字列の先頭が句読点であるときには一文字の調整をするというものですが、与えられた文字列が空だったり開始位置に文字列の長さが足りないなどの処理に加え、一文字しか入らなかった時となどの考慮が入って面倒になることが予想されます。

そこで、インスタンス生成の後に必要ならば禁則を行うメソッドを通すことにします。

String kinsoku = "、。,.";
String bun = "行頭に句読点が来る場合には、一つ前の文字から次の行にする。";
AjustString kp = new AjustString(g2,bun,hwhalf-4f);
kp.setOidashiIfNeeded(kinsoku);
if(kp.hasNext()) kp.drawKintou(hbas2+2f,vm+fh10*(++ln));
else             kp.drawLeft  (hbas2+2f,vm+fh10*(++ln));

これで、AjustStringのインスタンスkpを作って1行目に書く文字列が "行頭に句読点が来る場合には"までになったとします。

kpの内部では、String nfb="行頭に句読点が来る場合には" と int newcpbgn=13 が保持されます。

nfb残り....
一....
0 1 2 3 4 5 6 7 8 91011121314....

 ⇩

これを、kp.setOidashiIfNeeded(kinsoku)で、13文字目の"、"がkinsokuの文字列に含まれることを確認して、1行目に書く文字列を一つ少なくして nfb="行頭に句読点が来る場合に"し、newcpbgnの値を"は"の位置である12にします。

nfb残り....
一....
0 1 2 3 4 5 6 7 8 91011121314....

追い出しを行うメソッド

実際のsetOidashiIfNeeded()の中身を見てみます。

禁則にしたい文字の並び(今回は",.、。")を引数に呼び出すと、内部に保持しているnewcpbgnを使って次の行の最初の文字String nexttopを出します。これが禁則に引っかかると、nbfを減らし、newcpbgnを減らし、あまりの幅remmを計算し直します。

サロゲートペア対応なので少々面倒です。

間違えていました。訂正前 訂正後 です。(2017-6-26)

AjustString.javaにメソッド追加

public boolean setOidashiIfNeeded(String kindic){
    if (!hasNext()) return false;
    if (5>nbf.codePointCount(0, nbf.length())) return false;
    int newidxbgn = kp.offsetByCodePoints(0,newcpbgn);
    String nexttop = kp.substring(newidxbgn,kp.offsetByCodePoints(0,newcpbgn+1));
    if ( 0>kindic.indexOf(nexttop) ) return false;
    nbf = nbf.substring(0, nbf.codePointCount(0, nbf.length())-1);
    int newlengthincp = nbf.codePointCount(0,nbf.length())-1;
    int newlengthinindex = nbf.offsetByCodePoints(0, newlengthincp);
    nbf = nbf.substring(0, newlengthinindex);
    newcpbgn--;
    remm = fmwm - fm.stringWidth(nbf)*pt2mm;
    return true;
}

次の時には、メソッドが呼ばれても何もせず戻ります(実はfalseを返しています)。

1. この行で終わっているとき

2. 5文字以下の時(少ないと不自然になります)

使う側はインスタンスを作った後にメソッドを呼び出すだけ

禁則を適用したかどうかをbooleanで答えていますが、受け取っていません。今のところ使わなくても済んでいます。

強調部分を加えるだけです。

TsuuchiBun.java から呼び出す部分

String kinsoku = "、。";
List bundata;
if( (bundata = shokenmap.get(n))!=null ){
	for(String bun :bundata){
		kp = new AjustString(g2,bun,hwhalf-4f);
		kp.setOidashiIfNeeded(kinsoku);
		if(kp.hasNext()) kp.drawKintou(hbas2+2f,vm+fh10*(++ln));
		else             kp.drawLeft  (hbas2+2f,vm+fh10*(++ln));
		while((bgpt=kp.getNextPt()) > 0){
			kp = new AjustString(g2,bun,hwhalf-4f,bgpt);
			kp.setOidashiIfNeeded(kinsoku);
			if(kp.hasNext()) kp.drawKintou(hbas2+2f,vm+fh10*(++ln));
			else             kp.drawLeft  (hbas2+2f,vm+fh10*(++ln));
		}
	}
}

プログラム(TsuuchiBun.java)

package print01のメンバーとして次のクラスも必要です。

AjustString.java setOidashiIfNeeded()追加バージョン(このページ)
AjustStringT.java 罫線を引くのページ
Linemm.java 罫線を引くのページ
SetHRData.java setShokenMap(),getShokenMap()追加バージョン(このページ)
SelectIdNo.java 選択して印刷のページ

TsuuchiBun.java

package print01;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Line2D;
import java.awt.print.*;
import javax.print.attribute.HashPrintRequestAttributeSet;
import javax.print.attribute.HashPrintJobAttributeSet;
import javax.print.attribute.PrintRequestAttributeSet;
import javax.print.attribute.PrintJobAttributeSet;
import javax.print.attribute.standard.MediaSize;
import javax.print.attribute.standard.MediaSizeName;
import javax.print.attribute.standard.OrientationRequested;
import javax.print.attribute.standard.MediaPrintableArea;
import javax.print.attribute.standard.MediaTray;
import javax.print.attribute.Attribute;
import javax.swing.*;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static java.awt.RenderingHints.*;
//SwingUtilities.invokeLaterを使う--2--
//paintComponent()の振動抑制(non ImageBuffer第一号 見やすく訂正) --3--
//TsuuchiPrev.java=3 +Shukketsu
//2017-5-2 multipage print. preview yet.
//2017-5-3 add preview.
//2017-5-7 add select and preview, print.
//2017-5-11 add shoken kinsoku.
public class TsuuchiBun extends JPanel implements Printable, ActionListener {
    public static final double a4longside  = 297d*72/25.4; // 841.88;
    public static final double a4shortside = 210d*72/25.4; // 595.27;
    public static final double pwidth = a4shortside;
    public static final double pheight = a4longside;
    public static final double wZh = pwidth/pheight;
    String[] kams;
    int[] tans, tens, ks;
    Dimension prevsize, realsize;
    double scale;
    int nendo;
    int gakki = 0;
    int pmax ;
    int previewpage=1;
    JLabel statlabel;
    String HR;
    LinkedHashMap<Integer,String> namemap;    //番号=>氏名
    LinkedHashMap<Integer,LinkedHashMap<String,int[]>> seisekimap;    //番号=>(科目名=>成績配列[学期])
    LinkedHashMap<Integer,int[][]> shukketsumap;    //番号=>出欠配列[項目][学期]
    LinkedHashMap<String,Integer> tanis;    //科目名=>単位数
    LinkedHashMap<Integer,List<String>> shokenmap;
    public TsuuchiBun(
        LinkedHashMap<Integer,String> namemap,
        LinkedHashMap<Integer,LinkedHashMap<String,int[]>> seisekimap,
        LinkedHashMap<Integer,int[][]> shukketsumap,
        LinkedHashMap<String,Integer> tanis,
        LinkedHashMap<Integer,List<String>> shokenmap,
        int nendo,
        String HR,
        int gakki
    ){
        this.namemap = namemap;
        this.seisekimap = seisekimap;
        this.shukketsumap = shukketsumap;
        this.tanis = tanis;
        this.shokenmap = shokenmap;
        this.gakki = gakki;
        this.nendo = nendo;
        this.HR = HR;
        pmax = seisekimap.size();
        statlabel = new JLabel();
        setBackground(Color.white);
        prevsize = new Dimension();
        prevsize.setSize(pwidth,pheight);
        setPreferredSize(prevsize);
    }
    public void setStatLabel(JLabel lbl){
        this.statlabel = lbl;
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        if (command.equals("次")){
            previewpage++;
            if (previewpage>pmax) previewpage=1;
            repaint();
            statlabel.setText(String.format("%3d/%-3d",previewpage,pmax));
        }
           else if (command.equals("前")){
            previewpage--;
            if (0>=previewpage) previewpage = pmax;
            repaint();
            statlabel.setText(String.format("%3d/%-3d",previewpage,pmax));
        }
    }
    @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;
        if((double)(viewpw-vbarw)/viewph > wZh){
            prevsize.setSize(viewpw-vbarw, (viewpw-vbarw)/wZh);
            scale = (viewpw-vbarw)/pwidth;
        }else if( wZh > (double)viewpw / (viewph-hbarh)  ){
            prevsize.setSize((viewph-hbarh)*wZh, viewph-hbarh);
            scale = (viewph-hbarh)/pheight;
        }else{
            if ( (double)viewpw/viewph > wZh ) {
                prevsize.setSize(viewph*wZh, viewph);
                scale = viewph/pheight;
            }else{
                prevsize.setSize(viewpw, viewpw/wZh);
                scale = viewpw/pwidth;
            }
        }
        setPreferredSize(prevsize);
        //System.out.println("Tsuuchi pref. size:"+prevsize.width+"/"+prevsize.height);//test
        realsize=getSize(realsize); //test method of JPanel
        //System.out.println(realsize); //test
        //System.out.println(scale); //test
        g2.scale(scale,scale);
        drawPage(g2,previewpage-1);
    }
    @Override
    public int print(Graphics g, PageFormat pf, int pageIndex) {
        if (pageIndex >= pmax) return NO_SUCH_PAGE; //pageIndex is 0,1,2,...
        Graphics2D g2 = (Graphics2D)g;
        drawPage(g2,pageIndex);
        return PAGE_EXISTS;
    }

    public void drawPage(Graphics2D g2, int page){
        //2 Integer[] narray = namemap.keySet().toArray(new Integer[0]);
        //2 int n = narray[page];
        Object[] narray = namemap.keySet().toArray();
        int n = (int)narray[page];
        //x int[] narray = namemap.keySet().toArray(new int[0]);
        //x int n = narray[page];
        String name = namemap.get(n);
        LinkedHashMap<String,int[]> kamokutensmap = seisekimap.get(n);
        int kmax = kamokutensmap.size();
        int[][] ks = shukketsumap.get(n);
        //&amp use tanis for tani
        //g2.setRenderingHint(KEY_TEXT_ANTIALIASING,VALUE_TEXT_ANTIALIAS_GASP);
        g2.setRenderingHint(KEY_ANTIALIASING,VALUE_ANTIALIAS_ON);
        g2.setRenderingHint(KEY_TEXT_ANTIALIASING,VALUE_TEXT_ANTIALIAS_ON);
        //g2.setRenderingHint(KEY_TEXT_ANTIALIASING,VALUE_TEXT_ANTIALIAS_DEFAULT);
        float hbas = 22.0f;  //左端の位置
        float hwkm = 27.5f;  //科目名の幅
        float hwtn = 7f;     //単位数の幅
        float hptch = 45f/4;  //学期などの幅
        float vthtop = 41.01f; //ヘッダ行の上の位置
        float vtdtop = 64.01f; //行データの上の位置
        float vptch  = 9.8f;  //行データの各行の高さ
        float hwall = hwkm+hwtn+hptch*4;
        float mm2pt  = 72/25.4f;
        float pt2mm  = 25.4f/72;
        Font mfont11 = new Font("Serif", Font.PLAIN, 11);
        Font font18 = new Font("Serif", Font.PLAIN, 18);
        Font font14 = new Font("Serif", Font.PLAIN, 14);
        Font font12 = new Font("Serif", Font.PLAIN, 12);
        Font font10 = new Font("Serif", Font.PLAIN, 10);
        Font font09 = new Font("Serif", Font.PLAIN, 9);
        Font font08 = new Font("Serif", Font.PLAIN, 8);
        //Font font10 = new Font("IPA P明朝", Font.PLAIN, 10);
        BasicStroke boldstroke = new BasicStroke(1.0f);
        BasicStroke medmstroke = new BasicStroke(0.7f);
        BasicStroke thinstroke = new BasicStroke(0.0f);

        float vtitle = 24.2f;
        float vname = 35.0f;
        //出欠
        float hwssk = hwkm+hwtn+7f;
        float hwhalf= hwssk+hptch*4;
        float hbas2 = hbas+hwall+3.8f;
        float vwbik = vptch*8f;
        AjustString kp;
        g2.setFont(font18);
        kp = new AjustString(g2,"成績通知表",62f);
        kp.drawKintou(hbas+14.5f,vtitle);
        g2.setFont(font14);
        int g = (gakki>2)? 3: gakki;
        kp = new AjustString(g2, nendo+"年度 "+g+"学期",hwhalf);
        kp.drawCenter(hbas,vname);
        g2.setFont(font12);
        kp = new AjustString(g2, "第"+HR.substring(0,1)+"学年 "+HR.substring(1,2)+"組 "+n+"番",hwhalf);
        kp.drawCenter(hbas2,vtitle);

        g2.setFont(font14);
        float hwname1 = 40f;
        kp = new AjustString(g2, name, hwname1);
        if (!kp.hasNext()){
            kp.drawKintou(hbas2+(hwhalf-hwname1)/2,vname);
        }else{
            kp = new AjustString(g2, name, hwhalf);
            kp.drawCenter(hbas2,vname);
        }

        g2.setStroke(boldstroke);
        Linemm line = new Linemm();
        line.setTB(vthtop, vtdtop+vptch*kmax);
        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*kmax);
        g2.draw(line);
        g2.setStroke(medmstroke);
        line.setY(vtdtop);
        g2.draw(line);

        g2.setStroke(thinstroke);
        line.setLR(hbas, hbas+hwall);
        for (int k=1; kmax>k; k++){ //1 not 0
            line.setY(vtdtop+vptch*k);
            g2.draw(line);
        }
        line.setTB(vthtop, vtdtop+vptch*kmax);
        line.setX(hbas+hwkm);
        g2.draw(line);
        for (int i=0; 4>i; i++){
            line.setX(hbas+hwkm+hwtn+hptch*i);
            g2.draw(line);
        }
        g2.setFont(font10);
        float padbtm = (vptch-g2.getFontMetrics().getHeight()*pt2mm)/2;
        float vm,hm;
        kp = new AjustString(g2, "科目", hwkm-11f);
        kp.drawKintou(hbas+5.5f,vthtop+(vtdtop-vthtop)/2+g2.getFontMetrics().getHeight()*pt2mm/2);

        AjustStringT kt;
        kt = new AjustStringT(g2, "単位数" ,vtdtop-vthtop-6f);
        kt.drawTtoB(hbas+hwkm+hwtn/2,vthtop+3f);
        kt = new AjustStringT(g2, "一学期" ,vtdtop-vthtop-6f);
        kt.drawTtoB(hbas+hwkm+hwtn+hptch/2,vthtop+3f);
        kt = new AjustStringT(g2, "二学期" ,vtdtop-vthtop-6f);
        kt.drawTtoB(hbas+hwkm+hwtn+hptch*1.5f,vthtop+3f);
        kt = new AjustStringT(g2, "三学期" ,vtdtop-vthtop-6f);
        kt.drawTtoB(hbas+hwkm+hwtn+hptch*2.5f,vthtop+3f);
        kt = new AjustStringT(g2, "学年" ,vtdtop-vthtop-6f);
        kt.drawTtoB(hbas+hwkm+hwtn+hptch*3.5f,vthtop+3f);

        int kamokuichi = 0;
        //for (int k=0; kmax>k; k++){
        for (String kamokumei: kamokutensmap.keySet()) {
            kamokuichi++;
            vm = vtdtop+vptch*kamokuichi-padbtm;
            kp = new AjustString(g2, kamokumei ,hwkm-2f);
            
            if(kp.hasNext()){  //長い科目名を2行に
                
                kp.drawLeft(hbas+1f,vm+padbtm*3/4-vptch/2);
                kp = new AjustString(g2, kamokumei ,hwkm-2f, kp.getNextPt());
                kp.drawRight(hbas+1f,vm+padbtm/2);
                /* Fontを小さくする作戦
                g2.setFont(new Font("Serif", Font.PLAIN, 6));
                kp = new AjustString(g2, kams[k] ,hwkm-2f);
                kp.drawKintou(hbas+1f,vm);
                g2.setFont(font10);
                */
            }else{
            
                kp.drawKintou(hbas+1f,vm);
            }
            kp = new AjustString(g2, String.valueOf(tanis.get(kamokumei)), hwtn);//単位数 数値
            kp.drawCenter(hbas+hwkm,vm);
            g2.setFont(mfont11);
            int gk = 0;
            kp = new AjustString(g2, String.format("%3d",kamokutensmap.get(kamokumei)[gk]), hptch);//評点1 数値
            hm = hbas+hwkm+hwtn;
            kp.drawCenter(hm,vm);
            if(gakki>=2){
                gk = 1;
                kp = new AjustString(g2, String.format("%3d",kamokutensmap.get(kamokumei)[gk]), hptch);//評点2 数値
                kp.drawCenter(hm+hptch,vm);
            }
            if(gakki>=3){
                gk = 2;
                kp = new AjustString(g2, String.format("%3d",kamokutensmap.get(kamokumei)[gk]), hptch);//評点3 数値
                kp.drawCenter(hm+hptch*2,vm);
                kp = new AjustString(g2, String.format("%3d",kamokutensmap.get(kamokumei)[gk+1]), hptch);//評点5 数値
                kp.drawCenter(hm+hptch*3,vm);
            }
            g2.setFont(font10);
        }
        //出欠
        g2.setStroke(boldstroke);
        line.setLR(hbas2,hbas2+hwhalf);
        line.setY(vthtop);
        g2.draw(line); //Rectangle TOP
        line.setY(vtdtop);
        g2.draw(line); //Rectangle th/td
        line.setY(vtdtop+vptch*8);
        g2.draw(line); //Rectangle th/td
        line.setTB(vthtop,vtdtop+vptch*8);
        line.setX(hbas2);
        g2.draw(line); //Rectangle left
        line.setX(hbas2+hwhalf);
        g2.draw(line); //Rectangle right
        g2.setStroke(thinstroke);
        for(int gk=1;4>=gk;gk++){
            line.setX(hbas2+hwhalf-hptch*gk);
            g2.draw(line); //tate hwssk|hptch|hptch|...
        }
        line.setLR(hbas2,hbas2+hwhalf);
        for(int i=1;8>i;i++){
            line.setY(vtdtop+vptch*i);
            g2.draw(line);
        }
        kp = new AjustString(g2, "項目",hwssk-16.4f);
        kp.drawKintou(hbas2+8.2f,vtdtop-(vtdtop-vthtop)/2+g2.getFontMetrics().getHeight()*pt2mm/2f);
        kt = new AjustStringT(g2, "一学期",vtdtop-vthtop-6f);
        kt.drawTtoB(hbas2+hwssk+hptch/2f,vthtop+3f);
        kt = new AjustStringT(g2, "二学期",vtdtop-vthtop-6f);
        kt.drawTtoB(hbas2+hwssk+hptch+hptch/2f,vthtop+3f);
        kt = new AjustStringT(g2, "三学期",vtdtop-vthtop-6f);
        kt.drawTtoB(hbas2+hwssk+hptch*2+hptch/2f,vthtop+3f);
        kt = new AjustStringT(g2, "学年",vtdtop-vthtop-6f);
        kt.drawTtoB(hbas2+hwssk+hptch*3+hptch/2f,vthtop+3f);
        vm = vtdtop-padbtm;
        hm = hbas2;
        kp = new AjustString(g2,"授業日数",hwssk-4f);
        kp.drawKintou(hm+2f,vm+vptch);
        g2.setFont(font09);
        kp = new AjustString(g2,"出校停止・忌引等の日数",hwssk-4f);
        kp.drawKintou(hm+2f,vm+vptch*2);
        g2.setFont(font10);
        kp = new AjustString(g2,"出席すべき日数",hwssk-4f);
        kp.drawKintou(hm+2f,vm+vptch*3);
        kp = new AjustString(g2,"欠席日数",hwssk-4f);
        kp.drawKintou(hm+2f,vm+vptch*4);
        kp = new AjustString(g2,"出席日数",hwssk-4f);
        kp.drawKintou(hm+2f,vm+vptch*5);
        kp = new AjustString(g2,"遅刻",hwssk-4f);
        kp.drawKintou(hm+2f,vm+vptch*6);
        kp = new AjustString(g2,"早退",hwssk-4f);
        kp.drawKintou(hm+2f,vm+vptch*7);
        kp = new AjustString(g2,"欠課時数",hwssk-4f);
        kp.drawKintou(hm+2f,vm+vptch*8);

        g2.setFont(mfont11);
        int[] kssum = new int[6];
        for (int gk = 0; gakki>gk; gk++){
            hm = hbas2+hwssk+hptch*gk;
            int zd = ks[0][gk];
            kp = new AjustString(g2,String.valueOf(zd),hptch); //授業日数
            kp.drawCenter(hm,vm+vptch);
            kp = new AjustString(g2,String.valueOf(ks[1][gk]),hptch); //1:忌引き
            kp.drawCenter(hm,vm+vptch*2);
            kp = new AjustString(g2,String.valueOf(zd-ks[1][gk]),hptch);
            kp.drawCenter(hm,vm+vptch*3);
            kp = new AjustString(g2,String.valueOf(ks[2][gk]),hptch); //2:欠席
            kp.drawCenter(hm,vm+vptch*4);
            kp = new AjustString(g2,String.valueOf(zd-ks[1][gk]-ks[2][gk]),hptch);
            kp.drawCenter(hm,vm+vptch*5);
            kp = new AjustString(g2,String.valueOf(ks[3][gk]),hptch); //3: 遅刻
            kp.drawCenter(hm,vm+vptch*6);
            kp = new AjustString(g2,String.valueOf(ks[4][gk]),hptch); //4:早退
            kp.drawCenter(hm,vm+vptch*7);
            kp = new AjustString(g2,String.valueOf(ks[5][gk]),hptch); //5:欠課時数
            kp.drawCenter(hm,vm+vptch*8);
            for(int i=0; kssum.length>i; i++) kssum[i]+=ks[i][gk];
        }
        if(gakki==3){
            hm = hbas2+hwssk+hptch*3;
            int zd = kssum[0];
            kp = new AjustString(g2,String.valueOf(zd),hptch); //授業日数
            kp.drawCenter(hm,vm+vptch);
            kp = new AjustString(g2,String.valueOf(kssum[1]),hptch); //1:忌引き
            kp.drawCenter(hm,vm+vptch*2);
            kp = new AjustString(g2,String.valueOf(zd-kssum[1]),hptch);
            kp.drawCenter(hm,vm+vptch*3);
            kp = new AjustString(g2,String.valueOf(kssum[2]),hptch); //2:欠席
            kp.drawCenter(hm,vm+vptch*4);
            kp = new AjustString(g2,String.valueOf(zd-kssum[1]-kssum[2]),hptch);
            kp.drawCenter(hm,vm+vptch*5);
            kp = new AjustString(g2,String.valueOf(kssum[3]),hptch); //3:遅刻
            kp.drawCenter(hm,vm+vptch*6);
            kp = new AjustString(g2,String.valueOf(kssum[4]),hptch); //4:早退
            kp.drawCenter(hm,vm+vptch*7);
            kp = new AjustString(g2,String.valueOf(kssum[5]),hptch); //5:欠課時数
            kp.drawCenter(hm,vm+vptch*8);
        }
        g2.setFont(font10);

        //備考
        int bgpt = 0;
        g2.setFont(font08);
        vm = vtdtop+vptch*9f;
        kp = new AjustString(g2,"所見",8f);
        kp.drawKintou(hbas2+2f,vm+5f);
        g2.setStroke(boldstroke);
        line.setLR(hbas2,hbas2+hwhalf);
        line.setY(vm);
        g2.draw(line); //Rectangle TOP
        line.setY(vm+vwbik);
        g2.draw(line); //Rectangle bottom
        line.setTB(vm,vm+vwbik);
        line.setX(hbas2);
        g2.draw(line); //Rectangle left
        line.setX(hbas2+hwhalf);
        g2.draw(line); //Rectangle right
        g2.setFont(font10);
        float fh10 = (float)(g2.getFontMetrics().getHeight()*pt2mm*1.4f); //改行幅
        int ln=0;
        vm = vm + fh10+2f; //開始点
        String kinsoku = "、。,.";
        List<String> bundata;
        if( (bundata = shokenmap.get(n))!=null ){
            for(String bun :bundata){
                kp = new AjustString(g2,bun,hwhalf-4f);
                //kp.debug = true;
                kp.setOidashiIfNeeded(kinsoku);
                if(kp.hasNext()) kp.drawKintou(hbas2+2f,vm+fh10*(++ln));
                else             kp.drawLeft  (hbas2+2f,vm+fh10*(++ln));
                while((bgpt=kp.getNextPt()) > 0){
                    kp = new AjustString(g2,bun,hwhalf-4f,bgpt);
                    //kp.debug = true;
                    kp.setOidashiIfNeeded(kinsoku);
                    if(kp.hasNext()) kp.drawKintou(hbas2+2f,vm+fh10*(++ln));
                    else             kp.drawLeft  (hbas2+2f,vm+fh10*(++ln));
                }
            }
        }
        //return PAGE_EXISTS;
    }

     public static void main(String[] args) {
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }
    public static void createAndShowGUI() {
        int nendo = 2017;
        int gakki = 1;
        String HR = "1A";
        SetHRData hrdata = new SetHRData(nendo,HR,gakki);
        LinkedHashMap<Integer,String> namemap = hrdata.getNameMap();
        LinkedHashMap<Integer,LinkedHashMap<String,int[]>>
               seisekimap = hrdata.getSeisekiMap();
        LinkedHashMap<Integer,int[][]> shukketsumap = hrdata.getShukketsuMap();
        LinkedHashMap<String,Integer> tanis = hrdata.getTanis();
        LinkedHashMap<Integer,List<String>> shokenmap = hrdata.getShokenMap();

        PrinterJob pj = PrinterJob.getPrinterJob();
        PrintRequestAttributeSet reqset = new HashPrintRequestAttributeSet();
        MediaSizeName medname = MediaSizeName.ISO_A4;
        //MediaSizeName medname = MediaSizeName.JAPANESE_POSTCARD;
        reqset.add(medname);

        MediaSize medsize = MediaSize.getMediaSizeForName(medname);
        float medwidth  = medsize.getX(MediaPrintableArea.MM);
        float medheight = medsize.getY(MediaPrintableArea.MM);
        float topmm   = 8.8f;  //landscape前のTOP
        float bottomm = 7.7f;
        float leftmm  = 6.6f;
        float rightmm = 5.5f;
        reqset.add(new MediaPrintableArea(
           leftmm, topmm,
           (medwidth - leftmm - rightmm),
           (medheight - topmm - bottomm), MediaPrintableArea.MM));
        //reqset.add(OrientationRequested.REVERSE_LANDSCAPE);
        //reqset.add(OrientationRequested.LANDSCAPE);
        reqset.add(OrientationRequested.PORTRAIT);

        SelectIdNo shbox = new SelectIdNo(namemap);  //from here

        String[] printOrNot = {"画面で確認","印刷開始","中止"};
        int rev = JOptionPane.showOptionDialog(
                null, shbox,
                "印刷選択 ver"+shbox.ver,
                JOptionPane.OK_CANCEL_OPTION,
                JOptionPane.PLAIN_MESSAGE,
                null,printOrNot,printOrNot[0]
        );
        //System.out.println("rev1:"+rev);
        LinkedList<Integer> selectedno = shbox.getSelectedAsList();
        if (selectedno.size()==0) rev = 2;
        if (rev==2 || 0>rev) { //中止:2 close:-1
            System.out.println("Canceled");
        }
        else if (2>rev){
            LinkedHashMap<Integer,String> selnamemap = new LinkedHashMap<>();
            LinkedHashMap<Integer,LinkedHashMap<String,int[]>> selseisekimap =  new LinkedHashMap<>();
            LinkedHashMap<Integer,int[][]> selshukketsumap =  new LinkedHashMap<>();
            LinkedHashMap<Integer,List<String>> selshokenmap = new LinkedHashMap<>();
            for (int no:selectedno){
                selnamemap.put(no,namemap.get(no));
                selseisekimap.put(no,seisekimap.get(no));
                selshukketsumap.put(no,shukketsumap.get(no));
                selshokenmap.put(no,shokenmap.get(no));
            }
            TsuuchiBun prevPrintable = new TsuuchiBun(
                                selnamemap,selseisekimap,selshukketsumap,
                                tanis,selshokenmap,nendo,HR,gakki);  //printable instance xxx add sel-
            if (rev==0){//画面で確認
                JScrollPane scrollPane = new JScrollPane(prevPrintable);
                JPanel sbPanel = new JPanel();
                sbPanel.setLayout(new BorderLayout());
                sbPanel.add(scrollPane,BorderLayout.CENTER);
                JPanel btnPanel = new JPanel();
                sbPanel.add(btnPanel,BorderLayout.SOUTH);
                JButton upbtn = new JButton("次");
                JButton dnbtn = new JButton("前");
                upbtn.addActionListener(prevPrintable);
                dnbtn.addActionListener(prevPrintable);
                JLabel statlabel = new JLabel(String.format("%3d/%-3d",1,selseisekimap.size())); //xxx
                statlabel.setName("status");
                prevPrintable.setStatLabel(statlabel);
                btnPanel.add(dnbtn);
                btnPanel.add(statlabel);
                btnPanel.add(upbtn);
                String[] goOrNot = {"印刷","中止"};
                JOptionPane pane = new JOptionPane(
                                   sbPanel, 
                                   JOptionPane.PLAIN_MESSAGE, 
                                   JOptionPane.DEFAULT_OPTION,
                                   null,goOrNot,goOrNot[0]
                               );
                //JDialog dialog = pane.createDialog( null, "印刷プレビュー" );
                JDialog dialog = pane.createDialog("印刷プレビュー" );
                //ajust to screen size
                int dh = dialog.getPreferredSize().height;
                int dw = dialog.getPreferredSize().width;
                int scrh = Toolkit.getDefaultToolkit().getScreenSize().height;
                if (dh>scrh) {
                    dialog.setPreferredSize(new Dimension(dw,scrh-40));
                }
                dialog.pack();
                dialog.setResizable(true);
                dialog.setVisible(true);
                dialog.dispose(); //needs for ends /set HIDE_ON_CLOSE by default
                String svalue = (String)pane.getValue();
                rev = 1;//印刷へ
                if (svalue==null || svalue.equals("中止")) rev = 0; // System.exit(0);
            }
            if (rev ==1){//印刷
                pj.setPrintable(prevPrintable);  //printable instance
                System.out.println(pj.getPrintService().getName()); //test
                if (pj.printDialog( reqset )) {
                    boolean debug = false;
                    if(debug){
                        Attribute[] attrs = reqset.toArray(); //test
                        for(int n=0 ; attrs.length > n ; n++){ //test
                            System.out.println( attrs[n].getName()+":"+ attrs[n].toString()+";; ");
                        }
                    }
                    try { pj.print( reqset ); }
                    catch (PrinterException e) {
                        System.err.println(e);
                    }
                }
            }
        } //to here
        //dialog.dispose();
    }
}

プログラム(AjustString.java)

setOidashiIfNeeded()追加バージョンです。

AjustString.java

package print01;

import java.awt.Graphics2D;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.image.BufferedImage;

public class AjustString {
    String kp, nbf;
    int cpct, idxbgn, newcpbgn;
    float fmwm, remm;
    float aidamm = 0f;
    Graphics2D g;
    FontMetrics fm;
    float mm2pt = 72/25.4f;
    float pt2mm = 25.4f/72;
    boolean debug = false;
    /** formatWidthMm に入る mojiretsu を計算 */
    public AjustString(Graphics2D g, String mojiretsu, float wakuhaba) {
        this(g,mojiretsu,wakuhaba,0);
    }
    public AjustString(Graphics2D g, String mojiretsu, float wakuhaba, int kaishiichi) {
        this.g = g;
        kp = (mojiretsu!=null)? mojiretsu:"";  //nullなら""
        fmwm = (wakuhaba>=0)?wakuhaba:0;       //負なら0
        fm = g.getFontMetrics();
        cpct = kp.codePointCount(0,kp.length());  //kpのcp数
        if (0>kaishiichi) kaishiichi=0;       //負なら0
        if (cpct>kaishiichi){  //cpをindexに換算
            idxbgn = kp.offsetByCodePoints(0,kaishiichi);
            nbf = kp.substring(idxbgn);
        }else{             //開始位置が文字列をはみ出していたら
            idxbgn = kaishiichi-cpct+kp.length();
            nbf = "";
        }
        if(debug)System.out.println("cpct="+cpct+" /kaishiichi="+kaishiichi+" /idxbgn="+idxbgn+" /nbf="+nbf);//test
        remm = fmwm - fm.stringWidth(nbf)*pt2mm;  //あまりを計算
        newcpbgn = kaishiichi + nbf.codePointCount(0, nbf.length());  //書いた後の次の文字を計算
        if(debug)System.out.println( "nbf="+nbf+" /remm="+remm +" /nbf.length()="+nbf.length()+" /newcpbgn="+newcpbgn);//test
        while(0>remm && !nbf.isEmpty()) {
            int newendidx = nbf.offsetByCodePoints(nbf.length(), -1); //一つ前のindexを求める
            nbf = nbf.substring(0,newendidx);
            remm = fmwm - fm.stringWidth(nbf)*pt2mm;
            newcpbgn = kaishiichi + nbf.codePointCount(0, nbf.length());
            if(debug)System.out.println( "nbf="+nbf+" /remm="+remm +" /nbf.length()="+nbf.length()+" /newcpbgn="+newcpbgn);//test
        }
    }
    public static void main( String[] args ) {
        BufferedImage buffimg = new BufferedImage(400,300,BufferedImage.TYPE_INT_RGB);
        Graphics2D g = buffimg.createGraphics();
        g.setFont(new Font(Font.SERIF, Font.PLAIN, 10));
        float wakuhaba = (args.length>0) ? Float.parseFloat(args[0]):10f;
        int kaishiichi = (args.length>1) ? Integer.parseInt(args[1]):0;
        String mojitachi="東a西𠀋南ア北";
        mojitachi="東西𠀋南北春夏秋、冬喜怒哀楽";
        System.out.println("文字列="+mojitachi+" /枠幅(mm)="+wakuhaba+" /開始番号(0-)="+kaishiichi);
        AjustString as = new AjustString(g, mojitachi,wakuhaba,kaishiichi);
        as.setOidashiIfNeeded("、。");
    }
    public boolean setOidashiIfNeeded(String kindic){
        if (!hasNext()) return false;
        if (5>nbf.codePointCount(0, nbf.length())) return false;
        int newidxbgn = kp.offsetByCodePoints(0,newcpbgn);
        String nexttop = kp.substring(newidxbgn,kp.offsetByCodePoints(0,newcpbgn+1));
        if(debug)System.out.println( "nbf="+nbf+ " newcpbgn="+newcpbgn +" newidxbgn="+newidxbgn);
        if(debug)System.out.println( "nexttop=("+nexttop+")" );
        if ( 0>kindic.indexOf(nexttop) ) return false;
        //下記3行に訂正 nbf = nbf.substring(0, nbf.codePointCount(0, nbf.length())-1);
        int newlengthincp = nbf.codePointCount(0,nbf.length())-1;
        int newlengthinindex = nbf.offsetByCodePoints(0, newlengthincp);
        nbf = nbf.substring(0, newlengthinindex);
        newcpbgn--;
        remm = fmwm - fm.stringWidth(nbf)*pt2mm;
        if(debug)System.out.println( "xnbf="+nbf+ " xnewcpbgn="+newcpbgn );
        return true;
    }
    public boolean hasNext(){
        return cpct>newcpbgn;
    }
    public int getNextPt(){
        int retval = -1;
        if (cpct>newcpbgn) retval=newcpbgn;
        return retval;
    }
    public float getLastRemm(){
        return remm;
    }
    public void drawLeft(float hm, float vm) {
        g.drawString(nbf,hm*mm2pt,vm*mm2pt);
    }
    public void drawRight(float hm, float vm) {
        g.drawString(nbf,(hm+remm)*mm2pt,vm*mm2pt);
    }
    public void drawCenter(float hm, float vm) {
        g.drawString(nbf,(hm+remm/2)*mm2pt,vm*mm2pt);
    }
    public void drawKintou(float hm, float vm) {
        float hpp = 0;
        int nbflen = nbf.length();
        int nbfcplen = nbf.codePointCount(0,nbflen);
        if (nbfcplen!=1){
            aidamm = remm/(nbfcplen-1);
            int i=0;
            int nexti = 0;
            int cpct = 0;
            while (nbflen>i){ 
                nexti = nbf.offsetByCodePoints(i,1);
                g.drawString(nbf.substring(i,nexti),(hm+aidamm*cpct)*mm2pt+hpp,vm*mm2pt);
                hpp = fm.stringWidth(nbf.substring(0,nexti)); //pt
                i=nexti;
                cpct++;
            }
        }else{
            drawCenter( hm, vm);
        }
    }

}

プログラム(SetHRData.java)

setShokenMap(),getShokenMap()追加バージョン

この部分の解説はjavaで帳票印刷 文章を印刷(前のページ)参照

SetHRData.java

package print01;

import java.util.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.net.URL;
import static java.nio.charset.StandardCharsets.*;
//Test用
public class SetHRData{
    LinkedHashMap<Integer,String> namemap;    //番号=>氏名
    LinkedHashMap<Integer,LinkedHashMap<String,int[]>> seisekimap;    //番号=>(科目名=>成績配列[学期])
    LinkedHashMap<Integer,int[][]> shukketsumap;    //番号=>出欠配列[項目][学期]
    LinkedHashMap<String,Integer> tanis;    //科目名=>単位数
    LinkedHashMap<Integer,List<String>> shokenmap;
    int gakki,nendo, nmax;
    String HR;
    public SetHRData(int nendo, String HR, int gakki){
        this.nendo = nendo;
        this.HR = HR;
        this.gakki = gakki;
        String[] kams = { "国語総合","現代社会","数学Ⅰ","数学A","化学基礎",
                              "生物基礎","体育","保健","音楽Ⅰ",//"コミュニケーⅠ",
                              "コミュニケーション英語Ⅰ",
                              "英語表現Ⅰ","家庭基礎","情報の科学" };
        setNameMap();
        nmax = namemap.size();
        setSeisekiMap(kams);
        setShukketsuMap();
        setTanis(kams);
        setShokenMap();
    }
    //番号=>氏名
    private void setNameMap(){
        namemap = new LinkedHashMap<>();
        namemap.put(1,"赤石 美緒");
        namemap.put(2,"荒川 早織");
        namemap.put(3,"石村 千尋");
    }
    //番号=>(科目名=>成績配列[学期])
    private void setSeisekiMap(String[] kams){
        //seisekimap = new LinkedHashMap<Integer,LinkedHashMap<String,int[]>>();
        int[][] hitotens = {
                 { 55,45,65,45,75,45,55,55,60,45,65,65,55 }, //no.1
                 { 75,50,70,55,65,50,65,65,50,75,75,60,60 }, //no.2
                 { 85,65,75,40,70,55,60,70,65,55,60,85,95 } //no.3
        };
        seisekimap = new LinkedHashMap<>();
        for(int n=1; nmax>=n; n++){
            LinkedHashMap<String,int[]> kamokutens = new LinkedHashMap<>();
            for(int i=0; kams.length>i; i++){
                kamokutens.put(kams[i], new int[4]);
            }
            seisekimap.put(n, kamokutens);
        }
        for(int n=1; nmax>=n; n++){
            for(int k=0; hitotens[n-1].length>k; k++){
                seisekimap.get(n).get(kams[k])[gakki-1]= hitotens[n-1][k];
            }
        }
    }
    //番号=>出欠配列[項目][学期]
    private void setShukketsuMap(){
        //LinkedHashMap<Integer,int[][]> shukketsumap; //番号,出欠配列[項目][学期]
        int[][] ks = {
                { 62,1,3,1,0,18 }, //no.1
                { 62,0,2,0,1,13 }, //no.2
                { 62,2,1,0,1, 5 }  //no.3
        };
        shukketsumap = new LinkedHashMap<>();
        for(int n=1; nmax>=n; n++){
            shukketsumap.put(n, new int[6][3]);
        }
        for(int n=1; nmax>=n; n++){
            for(int k=0; ks[n-1].length>k; k++){
                shukketsumap.get(n)[k][gakki-1] = ks[n-1][k];
            }
        }
    }
    //科目名=>単位数
    private void setTanis(String[] kams){
        int[] tans = { 6,3,4,2,2,
                           2,2,1,1,2,4,
                           2,2,2 };
        //LinkedHashMap<String,Integer> tanis = new LinkedHashMap<>();
        tanis = new LinkedHashMap<>();
        for(int i=0; kams.length>i; i++){
            tanis.put(kams[i],tans[i]);
        }
    }
    private void setShokenMap(){
        shokenmap = new LinkedHashMap<Integer,List<String>>();
        for (int n=1;nmax>=n;n++){
            String fname = HR+String.format("%02d",n)+"tsu"+gakki+".txt";
            //System.out.println(nnn+":"+ipath);//test
            String pwd = getClass().getResource("").getPath()+"/data";
            List<String> list = null; //Collections.emptyList();
            Path path = Paths.get(pwd,fname);
            if(Files.notExists(path)) continue;
            try {
                //list = Files.readAllLines(path,  StandardCharsets.UTF_8 );
                list = Files.readAllLines(path, UTF_8 );
                if (list.size()>0) shokenmap.put(n,list);
            }catch (IOException e) {
                System.err.println( e);
            }
        }
    }

    public LinkedHashMap<Integer,List<String>> getShokenMap(){
        return shokenmap;
    }
    //番号=>氏名
    public LinkedHashMap<Integer,String> getNameMap(){
       return namemap;
    }
    //番号=>(科目名=>成績配列[学期])
    public LinkedHashMap<Integer,LinkedHashMap<String,int[]>> getSeisekiMap(){
       return seisekimap;
    }
    //番号=>出欠配列[項目][学期]
    public LinkedHashMap<Integer,int[][]> getShukketsuMap(){
       return shukketsumap;
    }
    //科目名=>単位数
    public LinkedHashMap<String,Integer> getTanis(){
       return tanis;
    }

     public static void main(String[] args) {
        //int nendo, String HR, int gakki
        SetHRData hrdata = new SetHRData(2017,"1A",1);  //printable instance
        LinkedHashMap<Integer,String> namemap = hrdata.getNameMap();
        LinkedHashMap<Integer,LinkedHashMap<String,int[]>>
               seisekimap = hrdata.getSeisekiMap();
        LinkedHashMap shukketsumap = hrdata.getShukketsuMap();
        LinkedHashMap tanis = hrdata.getTanis();
        System.out.println("...");
    }
}

プレビューのスクリーンショット

表題や名前が入りました。