PHPで文字列の折り返しと禁則処理

基本的な考え方としては、あらかじめ行頭や行末にきてほしくない文字を隣の文字とひとまとめにしておく。たとえば

なら

  • こ/れ/は/「チェッ/ク/ボッ/ク/ス」/で/す。

というように。
もちろんmonospaceな書体で、$widthは半角での文字数。

<?php

$kinsoku_c = str2array(')]})]}」』-、。ーぁぃぅぇぉっゃゅょゎァィゥェォッャュョヮヶ');
$kinsoku_p = str2array('([{([{「『');
$wodorizi = str2array('々ゝヽ');

function ja_wordwrap($str, $width)
{
    global $kinsoku_c, $kinsoku_p, $wodorizi;
    
    $token = ja_wordwrap_token($str, $kinsoku_c, $kinsoku_p);
    $lines = array();
    $tmp = '';
    for ($i = 0, $l = count($token); $i < $l; $i++) {
        if ($token[$i] == "\n") {
            $lines[] = $tmp;
            $tmp = '';
            continue;
        }
        $t = $tmp . $token[$i];
        if (mb_strwidth($t) > $width) {
            ja_wordwrap_next($lines, $tmp);
            $tmp = $token[$i];
        }
        else {
            $tmp = $t;
        }
    }
    if ($tmp != '') {
        ja_wordwrap_next($lines, $tmp);
    }
    return $lines;
}

function ja_wordwrap_next(&$lines, $tmp)
{
    global $kinsoku_c, $kinsoku_p, $wodorizi;
    
    if (count($lines) != 0 && in_array(mb_substr($tmp, 0, 1), $wodorizi)) {
        $prev = $lines[count($lines) - 1];
        $tmp = mb_substr($prev, mb_strlen($prev) - 1) . mb_substr($tmp, 1);
    }
    $lines[] = $tmp;
}

function ja_wordwrap_token($str)
{
    global $kinsoku_c, $kinsoku_p, $wodorizi;
    
    $token = array();
    $tmp = '';
    $prev = '';
    for ($i = 0, $l = mb_strlen($str); $i < $l; $i++) {
        $c = mb_substr($str, $i, 1);
        if ($c == "\n") {
            if ($tmp !== '') {
                $token[] = $tmp;
            }
            $token[] = "\n";
            $tmp = $prev = '';
        }
        else {
            if (in_array($prev, $kinsoku_p) == false &&
                in_array($c, $kinsoku_c) == false &&
                preg_match('/^\w\w$/', $c . $prev) == false) {
                if ($tmp !== '') {
                    $token[] = $tmp;
                    $tmp = '';
                }
            }
            $tmp .= $c;
            $prev = $c;
        }
    }
    if ($tmp !== '') {
        $token[] = $tmp;
    }
    
    return $token;
}

function str2array($str)
{
    $a = array();
    for ($i = 0, $l = mb_strlen($str); $i < $l; $i++) {
        $a[] = mb_substr($str, $i, 1);
    }

    return $a;
}

あと行頭に「々」がきてしまう場合に「云/々」→「云/云」したい。たぶんすぐできるよ。できたよ。
(追記) mb_strwidth を実際の幅で返すような関数にするとプロポーショナルフォントにも対応できる。その関数が遅い場合は、文字数からある程度アタリを取って高速化する。