文字列配置プログラムAdjustString.java
指定範囲に入りきらない場合に文字列を短くして入る分だけ印字するように改良します。範囲が十分な幅を持つ場合には前のプログラムと同様になります。
指定範囲が十分な幅を持たない場合の印字例は次のようになります。「AdjustStringOvf.java」と同じで、環境が許せば、【丈`】をサロゲートペアを使う本来の字に入れ替えて同じ印字を得ることができます。
プログラムです。
/**
指定範囲に文字列を均等割付、右寄せ、左寄せ、センタリングなどの調整をして描画する
入りきらない場合は、入りきる分だけの部分文字列を保持して
均等割付、左寄せなどの指示により部分文字列を描画する
2019-9-20
@author Adachi
@version 3.0
*/
import java.awt.Graphics2D;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.image.BufferedImage;
public class AdjustString {
String zstr, str; //motono string, string
int zcpct,cpct; //code point count
int bgncpi, nxtbgncpi; //cp index begin, next cp index begin
float wdmm; //width in mm
float remm = 0f; //remain in mm;
float gapmm = 0f;
Graphics2D g2;
FontMetrics fm;
float mm2pt = 72/25.4f;
float pt2mm = 25.4f/72;
boolean debug = true;
/** areawidth に string を入れる時の余り remm を計算 */
public AdjustString(Graphics2D g2, String string, float areawidth) {
this(g2,string,areawidth,0);
}
/** areawidth に string を入れる時の余り remm を計算。ただし str の bgncpiから */
public AdjustString(Graphics2D g2, String string, float areawidth, int begincpindex) {
this.g2 = g2;
fm = g2.getFontMetrics();
zstr = (string!=null) ? string : ""; //nullなら""
wdmm = (areawidth>=0f) ? areawidth : 0f; //負なら0
zcpct = zstr.codePointCount(0,zstr.length()); //zstrのcp数
bgncpi = (begincpindex>=0) ? begincpindex : 0; //負なら0
if (zcpct>bgncpi){ //開始位置をindexに換算
int idxbgn = zstr.offsetByCodePoints(0,bgncpi);
str = zstr.substring(idxbgn);
}else{ //開始位置が文字列をはみ出していたら
str = "";
}
remm = wdmm - fm.stringWidth(str)*pt2mm; //あまりを計算
if(debug){System.out.println(report());}
while(0>remm && !str.isEmpty()) {
int newendidx = str.offsetByCodePoints(str.length(), -1); //一つ前のindexを求める
str = str.substring(0,newendidx);
remm = wdmm - fm.stringWidth(str)*pt2mm;
if(debug){System.out.println(report());}
}
cpct = str.codePointCount(0,str.length());
nxtbgncpi = bgncpi + cpct;
gapmm = remm/(cpct-1);
}
/** 文字列・余り・文字数 をレポートする。(デバッグ用) */
public String report(){
cpct = str.codePointCount(0,str.length());
nxtbgncpi = bgncpi + cpct;
String rstr = String.format("str=%s /remm=%f /str.length()=%d /cpct=%d /nxtbgncpi=%d",
str, remm, str.length(), cpct, nxtbgncpi);
return rstr;
}
/** zstr のなかで書ききれなかった残りがあるかを答える */
public boolean hasNext(){
return zcpct>nxtbgncpi;
}
/** 書ききれなかった残りの文字の開始位置(コードポイントで)を答える */
public int getNextPt(){
int retval = -1;
if (zcpct>nxtbgncpi) retval=nxtbgncpi;
return retval;
}
/** strがareawidth のなかに収まるかを答える */
public boolean isFitInto(){
return remm>=0;
}
/** 文字列の幅を引いたあまりをmmで返す */
public float getRemain(){
return remm;
}
/** 文字間の隙間をmmで返す */
public float getGap(){
return gapmm;
}
/** 書けるだけの文字列を左寄せで描画する */
public void drawLeft(float hm, float vm) {
g2.drawString(str,hm*mm2pt,vm*mm2pt);
}
/** 書けるだけの文字列を右寄せで描画する */
public void drawRight(float hm, float vm) {
g2.drawString(str,(hm+remm)*mm2pt,vm*mm2pt);
}
/** 書けるだけの文字列をセンタリングで描画する */
public void drawCenter(float hm, float vm) {
g2.drawString(str,(hm+remm/2)*mm2pt,vm*mm2pt);
}
/** 書けるだけの文字列を均等割付で描画する */
public void drawKintou(float hm, float vm) {
float hpp = 0;
int strlen = str.length();
if (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),(hm+gapmm*ct)*mm2pt+hpp,vm*mm2pt);
hpp = fm.stringWidth(str.substring(0,nexti)); //pt
i=nexti;
ct++;
}
}else{
drawCenter(hm, vm);
}
}
public static void main( String[] args ) {
BufferedImage buffimg = new BufferedImage(800,600,BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = buffimg.createGraphics();
float w = (args.length>0) ? Float.parseFloat(args[0]):50f;
int bgncpi = (args.length>1) ? Integer.parseInt(args[1]):0;
String str="東西【丈`】南北";
System.out.println("文字列="+str+" /枠幅(mm)="+w+" /開始番号(0-)="+bgncpi);
float x = 30.1f;
float y = 30.2f;
float h = 16.3f;
AdjustString ads = new AdjustString(g2, str,w,bgncpi);
System.out.println("--main--");
System.out.println(ads.report());
System.out.println(ads.getGap());
ads.drawKintou(x,y+h);
}
}
コンストラクタ以外は「AdjustStringOvf.java」とほとんど違いはありません。コンストラクタではまず書きれなかった時に一文字ずつ減らして書ききれる分だけの文字列(str)を保持するようにします。後のdrawKintou()などのメソッドではこのstrを印字対象にします。
最初の実行例は引数に10を渡して10mm中に書ける文字列(str)を決めるまでの過程を見ています。remmが正の数になるまで文字を減らしてついに2文字にします。--main--までの出力がそれです。これは動作確認のために出しているもので、実際の運用では boolean debug = true;をfalseにすることにより抑制します。
--main--から下の出力は、AdjustString のコンストラクタを呼び出した後のものです。AdjustString のインスタンスadsから情報を得たあと、adsインスタンス内の(str)を均等割付けで印字させています。ただし、このmain()ではBufferedImageに描画するだけで実際の印刷はしません。
adachi@debian64:~$ java AdjustString 10 文字列=東西【丈`】南北 /枠幅(mm)=10.0 /開始番号(0-)=0 str=東西【丈`】南北 /remm=-11.166666 /str.length()=6 /cpct=5 /nxtbgncpi=5 str=東西【丈`】南 /remm=-6.933334 /str.length()=5 /cpct=4 /nxtbgncpi=4 str=東西【丈`】 /remm=-2.700000 /str.length()=4 /cpct=3 /nxtbgncpi=3 str=東西 /remm=1.533333 /str.length()=2 /cpct=2 /nxtbgncpi=2 --main-- str=東西 /remm=1.533333 /str.length()=2 /cpct=2 /nxtbgncpi=2 1.5333328
つぎに、文字列の残りの部分を書くことを想定して、引数を「10 2」として再び実行したものを以下に示します。「10 2」は10mm中に、2番目の文字(0から数える)から実行するという意味です。やはり、--main--までの出力でわかるとおり、remmが正の数になるまで文字を減らしてついに2文字にします。ただし、【丈`】がサロゲートペアからなる一文字であることに注意してください。
adachi@debian64:/media/adachi/S1T/java/print04$ java AdjustString 10 2 str=【丈`】南北 /remm=-2.700000 /str.length()=4 /cpct=3 /nxtbgncpi=5 str=【丈`】南 /remm=1.533333 /str.length()=3 /cpct=2 /nxtbgncpi=4 --main-- str=【丈`】南 /remm=1.533333 /str.length()=3 /cpct=2 /nxtbgncpi=4 1.5333328
AdjustString.javaを使って帳票を印刷する例
前項のPrintableWithAdjustStringOvf.javaの中のAdjustStringOvfをAdjustStringに替えると同様の印字結果が得られますが、それは省略して、書ききれない場合は2行にするというのを試してみます。
使い方の骨子は次のようなものです。hasNext()がtrueを返す場合は、入りきらないので、左寄せで書けるだけ書き、getNextPt()で残された文字列の位置を得て、それを引数に新たなインスタンスを作り、今度は右寄せにします。
AdjustString ads = new AdjustString(g2,str,w);
if(ads.hasNext()){
ads.drawLeft(x,y-charh/2);
ads = new AdjustString(g2,str,w,ads.getNextPt());
ads.drawRight(x,y+charh/2);
}else{
ads.drawKintou(x,y);
}
印字が重ならないようにcharh/2を引いたり、足したりしていますが、charhは文字の高さです。この例では2行以上になることはないという仮定で考えています。
プログラムです。
import java.awt.*;
import java.awt.print.*;
import javax.print.*;
import javax.print.attribute.*;
import javax.print.attribute.standard.*;
/** PrintableWithAdjustString2.java
AdjustString.javaを使い、均等割付印刷をします。
1行に入りきらない時は2行に分けます。
2019-05-26 adachi
*/
public class PrintableWithAdjustString2 implements Printable {
@Override
public int print(Graphics g, PageFormat pf, int pageIndex) {
if (pageIndex != 0) return NO_SUCH_PAGE;
Graphics2D g2 = (Graphics2D)g;
g2.setFont(new Font(Font.SERIF, Font.PLAIN, 36));
float mm2pt = 72/25.4f;
String str1="東西【丈`】南北";
String str2="黒茶赤橙黃緑青紫灰白";
String str3="鱈鮭鰯【魚花】";
float x = 20.1f;
float y = 20.2f;
float w = 100f;
float h = 24.3f;
float padx = 3f;
float pady = 2f;
float charh = g2.getFontMetrics().getFont().getSize()/mm2pt;
g2.setColor(Color.BLACK);
AdjustString ads = new AdjustString(g2,str1,w-padx*2);
ads.drawKintou(x+padx,y+h-pady);
ads = new AdjustString(g2,str2,w-padx*2);
if(ads.hasNext()){ //長い文字列を2行に
ads.drawLeft(x+padx,y+2*h-pady-charh/2);
ads = new AdjustString(g2, str2 ,w-padx*2, ads.getNextPt());
ads.drawRight(x+padx,y+2*h-pady+charh/2);
}else{
ads.drawKintou(x+padx,y+2*h-pady);
}
ads = new AdjustString(g2,str3,w-padx*2);
ads.drawKintou(x+padx,y+3*h-pady);
return PAGE_EXISTS;
}
public static void main(String[] args) {
Printable pable = new PrintableWithAdjustString2();
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));
PrinterJob pj = PrinterJob.getPrinterJob();
pj.setPrintable(pable);
if (pj.printDialog(rqset)) {
try { pj.print(rqset); }
catch (PrinterException e) {
System.err.println(e);
}
}
}
}
実行結果です。"黒茶赤橙黃緑青紫灰白"が入らないので入る分を左寄せにし、残りを右寄せにして、均等割付けの左右に合わせます。
この3つの文字列の中で、"【丈`】"と"【魚花】"(注2)がサロケートペアの文字です。
注2
CodeZineのサイトではCMSの仕様でサロゲートペアの文字が扱えないため、「U+29e3d」の文字(ほっけ)を【魚花】(実際は1文字)と表記しています。
debug=trueのままなので、文字列調整の端末に途中経過がでます。実際の運用ではdebug=falseにしておくのが良いでしょう。
adachi@debian64:~$ java PrintableWithAdjustString2 str=東西【丈`】南北 /remm=30.500000 /str.length()=6 /cpct=5 /nxtbgncpi=5 str=黒茶赤橙黃緑青紫灰白 /remm=-33.000000 /str.length()=10 /cpct=10 /nxtbgncpi=10 str=黒茶赤橙黃緑青紫灰 /remm=-20.300003 /str.length()=9 /cpct=9 /nxtbgncpi=9 str=黒茶赤橙黃緑青紫 /remm=-7.599998 /str.length()=8 /cpct=8 /nxtbgncpi=8 str=黒茶赤橙黃緑青 /remm=5.099998 /str.length()=7 /cpct=7 /nxtbgncpi=7 str=紫灰白 /remm=55.900002 /str.length()=3 /cpct=3 /nxtbgncpi=10 str=鱈鮭鰯【魚花】 /remm=43.200001 /str.length()=5 /cpct=4 /nxtbgncpi=4
