SSブログ

禁則処理の組み方 [日記]

テキストの表示を行う際に、一定の長さで自動的に折り返す機能が欲しくなる時があります。
一般的にワードラップとよばれる機能です。
ワードラップの機能は、テキストの内容が一定だったり幅が既に決まっている場合は、テキストそのものに改行を入れることで対応ができます。
しかし入力されるテキストが不定だったり、テキストの表示幅やフォントのサイズが可変だったりすると、その状況に合わせて改行位置を修正してやる必要が出てきます。

ワードラップの実装は比較的簡単で、1文字ごとの長さを足していき、所定の長さに達したら改行を挟み込むという形になります。

string    baseText;
string    resultText = "";
int        maxWidth = 200;
int        nowWidth = 0;
for (var i = 0; i < baseText; i++) {
    var    ch = baseText[i];
    if (ch == '\n') {
        nowWidth = 0;
    } else {
        var    charWidth = GetCharacterWidth(ch);        // 文字の幅を取得する
        if ((nowWidth + charWidth) > maxWidth) {
            // 改行を挟む
            resultText += '\n';
            nowWidth = 0;
        }
        nowWidth += charWidth;
    }

    resultText += ch;
}

上記の処理で所定の範囲内にテキスト表示が行えるようになりましたが、ちょっとした問題も起きます。
いわゆる禁則処理です。
禁則処理とは簡単に言うと、改行をしてはいけない場所があるということです。

(例)
吾輩は、たぶん「猫」なのであーる。

(×)
********
吾輩は、たぶん「
猫」なのであーる


(○)
********
吾輩は、たぶん
「猫」なのであー
る。

上記の例では、「の後ろで折り返している点と句点が行頭に来ている点が問題になります。
さらに英文にも「単語の途中で折り返さない」という似たような法則があります。

(例)
My father worked in a game company until last year.

(×)
************************
My father worked in a ga
me company until last ye
ar.

(○)
************************
My father worked in a
game company until last
year.

これらの禁則処理を上記のソースコードに対応させます。
基本的な考え方は「折り返しの必要が出た場合に、どこで折り返せるかを記憶しておく」形になります。

string    baseText;
string    resultText = "";
int        maxWidth = 200;
int        nowWidth = 0;
int        wordwrapBasePos = -1;
int        wordwrapResultPos;
int        wordwrapWidth;
for (var i = 0; i < baseText.length; i++) {
    var    ch = baseText[i];
    if (ch == '\n') {
        nowWidth = 0;
        wordwrapBasePos = -1;
    } else {
        var    charWidth = GetCharacterWidth(ch);        // 文字の幅を取得する

        // 禁則文字かチェック
        if (((nowWidth + charWidth) > maxWidth) || !IsFrontKinsoku(ch)) {    // (A)
            if (i != (baseText.length - 1) && !IsFrontKinsoku(baseText[i + 1])) {    // (B)
                if (!IsEnglishKinsoku(ch) && !IsBackKinsoku(ch)) {    // (C)
                    // この文字の後ろに改行を入れることができる
                    wordwrapBasePos   = i;
                    wordwrapResultPos = resultText.length;
                    wordwrapWidth     = nowWidth;
                }
            }
        }
        if ((nowWidth + charWidth) > maxWidth) {
            // テキストが指定幅をオーバーする
            if (wordwrapBasePos >= 0 && wordwrapBasePos != i) {
                // 改行を挟む
                resultText = resultText.Substring(0, wordwrapResultPos + 1);
                resultText += '\n';
                nowWidth = 0;
                i = wordwrapBasePos;
                wordwrapBasePos = -1;
                continue;
            }

            // 改行を挟む
            resultText += '\n';
            nowWidth = 0;
            wordwrapBasePos = -1;
        }
        nowWidth += charWidth;
    }

    resultText += ch;
}

このような感じになります。
まずはチェック部分。
禁則には行末に来てはいけない文字(「{【< など)と、行頭に来てはいけない文字(」}】>、。 など)があります。
行頭に来てはいけない文字かどうかをチェックするのがIsFrontKinsoku()、行末に来てはいけない文字かどうかをチェックするのがIsBackKinsoku()になります。
英文として折り返していい文字かどうかをチェックするのがIsEnglishKinsoku()です。

(A)では次に追加する文字の手前で折り返すことが確定していて、次の文字が行頭に来てはいけない文字かどうかをチェックしています。
(B)では次の次に追加する文字が、行頭に来てはいけない文字かどうかをチェックしています。
(C)では次に追加する文字が、行頭に来てはいけない文字か英文として折り返していい文字かどうかをチェックしています。
これらを全てクリアすると、次に追加する文字の後ろに改行を入れていい場所ということになります。
wordwrapBasePosが0以上の場合、指定された場所までテキストを巻き戻して、改行を追加して引き続き処理を行います。

さてこれで折り返し自体は出来るようになります。
しかし実際に表示してみると、文章によっては折り返し位置が各行でバラバラになってしまい、見栄えが悪いことがあります。
一太郎などを良く見てみると、1行毎に文字数に合わせて字間を調整しているのが分かると思います。
これを行うことで、1行の長さが大体同じになり、見た目的に綺麗になります。


2014-06-17 19:10  nice!(1)  コメント(0)  トラックバック(0) 
共通テーマ:パソコン・インターネット

nice! 1

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証: 下の画像に表示されている文字を入力してください。

 

このブログの更新情報が届きます

すでにブログをお持ちの方は[こちら]


この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。