VisualforceページPDFで日本語改行させるときの汎用メソッド

# PDFで日本語がうまく改行されないやつ
VisualforceページでPDF作るときにどうしてもぶつかるのが、自動改行されない問題。
ある程度文字数で改行しても、等幅フォントではないので英数字で見え方にかなり異なってくる。
今回はその時に利用可能なメソッドの備忘。あくまでも概算なので、もっと厳密にできるとは思うんですが。

# サンプルコード
```apex:CalcFontWidth.cls
/**
 * テキストエリアのような長文の改行コードをHTML用に変換する。
 * 自動改行されない箇所も指定文字数で改行する
 * @param String 置換前の文字列, Integer 改行したい文字幅（バイト数に係数をかけたものを幅とする：半角 1.25、全角 2.25）
 * @param Integer lbn
 * @return String 置換後の文字列
 */
private String replaceLinebreakCode(String beforeStr, Integer lbn){

    // 置換前文字列チェック
    if( String.isBlank(beforeStr) ){
        return null;
    }
    // 分割文字数チェック
    if( lbn == 0 || lbn == null ){
        return null;
    }
    String afterStr = '';

    // あらかじめ改行されている行を１StringとしてListに格納
    List<String> bfrStrs = new List<String>();
    String linebreak = System.label.LineBreak.replace('-', '');
    beforeStr = beforeStr.replaceAll('\r\n','★改行★').replaceAll('\n','★改行★').replaceAll(linebreak, '★改行★');
    bfrStrs = beforeStr.split('★改行★');

    // 1行ずつループするし、1行が指定文字数に収まるように分割し、返却用文字列を作成する
    for(String str :bfrStrs){
        String line = '';
        Decimal lineBites = 0;
        List<String> splitStr = str.split('');
        // 1文字ずつループする
        for(String oneChar :splitStr){
            // 半角・全角によって前提となる文字幅を調整する
            Decimal fontLength = calcFontWidth(oneChar);

            // 1文字追加した場合に1行に収まるか計算し、収まらなかった場合は次の行に移行する
            if( lineBites + fontLength > lbn){
                afterStr += line;
                afterStr += '★改行★';
                line = oneChar;
                lineBites = fontLength;
            }else{
                line += oneChar;
                lineBites += fontLength;
            }
        }
        afterStr += line;
        afterStr += '★改行★';
    }
    // 一番最後のbrを削除する
    afterStr = afterStr.escapeHtml3().removeEnd('★改行★').replace('★改行★','<br/>');

    return afterStr;
}

/**
 * 全角文字・半角文字(半角の場合はさらに幅分類)別の文字幅を概算する
 * @param String 判定対象文字
 * @return Decimal 文字幅概算
 */
private Decimal calcFontWidth(String oneChar){
    // [Arial Unicode MS]は等幅フォントでないため、著しくサイズが異なる文字のみ別判定する
    String superWideCases = 'mMW';
    String veryWideCases = 'GOQ';
    String wideCases = 'wABCDEFHKNPRSTUVXYZ';
    String narrowCases = 'ftI';
    String veryNarrowCases = 'ijl';

    Decimal fontLength = isHalfChar(oneChar)? 1.25 : 2.25;

    // アルファベットのうち、大文字・太めの小文字・細めの小文字を判定して微調整する
    if( superWideCases.contains(oneChar) ){
        fontLength += 0.9;
    } else if( veryWideCases.contains(oneChar) ){
        fontLength += 0.6;
    } else if( wideCases.contains(oneChar) ){
        fontLength += 0.45;
    } else if( narrowCases.contains(oneChar) ){
        fontLength -= 0.6;
    } else if( veryNarrowCases.contains(oneChar) ){
        fontLength -= 0.75;
    }

    return fontLength;
}

/**
 * 全角文字・半角文字をbyte数にて判断する
 * @param String 判定対象文字
 * @return Boolean 半角文字の場合Trueを返却する
 */
private Boolean isHalfChar(String oneChar){
    Boolean isHalf = false;
    Integer charLength = Blob.valueOf(oneChar).size();
    if( charLength == 1 ){
        isHalf = true;
    }

    return isHalf;
}

```
## 使い方
`replaceLinebreakCode(String beforeStr, Integer lbn)`に改行したい文章と、1行当たりの長さを渡したら、`<br/>`で改行済みの文字列が返ってきます。
page側では`<apex:outputText value="{!userName}" escape="false" />`みたいな感じで、escape設定して出力いただくとよいと思います。

## 備忘
実際に使うときは、`Integer lbn`で行の長さを調整して、文字幅とかもいい感じに微調整してもらえばいいのかと。
今回はしてないけど、記号が頻出の時は記号幅調整対象にしていけばよろし。