前回、 「簡易的な」 役物詰めを紹介しましたが、 禁則処理の問題などが残っているので解決していきます。
現状の問題点
現状で把握できている問題点は、
の二点です。 順に見ていきます。
連続する句読点のぶら下がり処理
句読点が連続する場合はぶら下がり処理を行わず、 ただの半角役物として扱います。
php (正規表現式)
// 句読点の検索 (単数の句読点のみ、 ぶら下がり)
'#' .
'([、。,. ]{2,})' .
'|' .
'([、。,. ])' .
'#uis' => function ( $match ) {
if ( $match[1] ) {// 句読点が2文字以上続いている場合
return
'<span class = "thx_punc_punc">' .
$match[1] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
} else { // 句読点が1文字のみは、 ぶら下がり
return
'<span class = "thx_punc_wrap">' .
'<span class = "thx_punctuation">' .
$match[2] .
'</span>' .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
},
正規表現の条件式を、 「2文字以上」 と 「1文字のみ」 とに場合分けをします。
「2文字以上」 の場合は、 ただの半角役物として 「.thx_punc_punc」 を付与し、 可読性を考慮して 「.thx_clps_spc」 の半角スペースを追加します。
css (追加分)
.thx_punc_punc {
font-feature-settings: "halt";
}
半角スペースによる禁則処理が効かない改行
半角スペースが存在する限り、 そこは改行可能になってしまいます。
これを回避するには、 禁則処理が効かなくなってしまう不要な半角スペースを取り除く必要があります。
これは must です。
となると、 連続する括弧の場合、 両端以外の半角スペースを除去する必要があるため、 括弧がすべて半角送りになってしまいます。 詰まり過ぎて可読性が落ちてしまうのでは?と心配になりますが、
こうして比べてみると、 どちらにも良さがあり、 「甲乙付け難い」 と感じます。
そこで、 どちらが一般的なのか調べてみると、 とても解り易いページがありました。
この文書は、 w3.org が JIS X 4051 (日本語組版規則) に基づいて記述しているようで、 この上ない指標・ルールと成り得そうです。
こちらの記述によると、 連続する同種括弧はベタ組みとなっているので、 それに倣います。
php (正規表現式)
// 括弧の検索
'#' .
'[ ({[〔「『【〈《]+' .
'#uis' => function ( $match ) {
return
'<span class = "thx_clps_spc"> </span>' .
'<span class = "thx_opening_bracket">' .
$match[0] .
'</span>';
},
'#' .
'[)}]〕」』】〉》 ]+' .
'#uis' => function ( $match ) {
return
'<span class = "thx_closing_bracket">' .
$match[0] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
},
正規表現の条件式を、 「1文字以上」 に変更し、 始め括弧・終わり括弧のそれぞれが連続する場合は一つの span でまとめます。
また、 両端 (括弧の外側) には従来通りに 「.thx_clps_spc」 の半角スペースを追加します。
css (追加分)
特になし。
更に、 この文書をよく見てみると、 句読点と終わり括弧もベタ組みになっています。 つまり、 こんな感じに更に詰めよと。
ふむ、 となると、 句読点と終わり括弧は併せて処理したほうが良さそうですね。 改めます。
句読点と終わり括弧の処理
php (正規表現式)
// 句読点と終わり括弧の検索 (後端単数の句読点のみ、 ぶら下がり)
'#' .
'([、。,.)}]〕」』】〉》 ]{2,})' .
'|' .
'([、。,. ])' .
'|' .
'([)}]〕」』】〉》 ])' .
'#uis' => function ( $match ) {
// 句読点や終わり括弧が2文字以上続いている場合
if ( $match[1] ) {
// 後端の1文字が句読点で、 後端の2文字が同一でなければ、 ぶら下がり処理
if (
(
' 、 ' === mb_substr( $match[1], -1 )
||
' 。 ' === mb_substr( $match[1], -1 )
||
' , ' === mb_substr( $match[1], -1 )
||
' . ' === mb_substr( $match[1], -1 )
)
&&
( mb_substr( $match[1], -1, 1 ) !== mb_substr( $match[1], -2, 1 ) )
) {
return
'<span class = "thx_closing_mark">' .
mb_substr( $match[1], 0, -1 ) .
'</span>' .
'<span class = "thx_punc_wrap">' .
'<span class = "thx_punctuation">' .
mb_substr( $match[1], -1 ) .
'</span>' .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
} else { // ぶら下がり不要
return
'<span class = "thx_closing_mark">' .
$match[1] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
} elseif ( $match[2] ) { // 句読点が1文字のみは、 ぶら下がり
return
'<span class = "thx_punc_wrap">' .
'<span class = "thx_punctuation">' .
$match[2] .
'</span>' .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
} else { // ぶら下がり不要
return
'<span class = "thx_closing_mark">' .
$match[3] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
},
'#' .
'([、。,.)}]〕」』】〉》 ]{2,})' .
'|' .
'([、。,. ])' .
'|' .
'([)}]〕」』】〉》 ])' .
'#uis'
正規表現の条件式を、
- 句読点か終わり括弧が2文字以上
- 句読点が1文字のみ
- 終わり括弧が1文字のみ
とに場合分けをします。
句読点か終わり括弧が2文字以上
if (
(
' 、 ' === mb_substr( $match[1], -1 )
||
' 。 ' === mb_substr( $match[1], -1 )
||
' , ' === mb_substr( $match[1], -1 )
||
' . ' === mb_substr( $match[1], -1 )
)
&&
( mb_substr( $match[1], -1, 1 ) !== mb_substr( $match[1], -2, 1 ) )
)
「句読点か終わり括弧が2文字以上」 の場合、 ぶら下がり処理が必要な可能性があるので、 判別を行います。
判別の基準は、
- 後端の1文字が句読点で終わっている
且つ、
- 後端の2文字が同一ではない
とします。
{
return
'<span class = "thx_closing_mark">' .
mb_substr( $match[1], 0, -1 ) .
'</span>' .
'<span class = "thx_punc_wrap">' .
'<span class = "thx_punctuation">' .
mb_substr( $match[1], -1 ) .
'</span>' .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
上記の判別基準に合致した場合、 まずは後端の1文字を除いた部分に 「thx_closing_mark」 を付与し、 半角送りとします。
続いて、 後端の1文字にぶら下がり処理を行います。
css
/* 括弧など */
.thx_closing_mark {
font-feature-settings: "halt";
}
else { // ぶら下がり不要
return
'<span class = "thx_closing_mark">' .
$match[1] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
合致しない場合はぶら下がり処理の必要がないので、 検出された 「句読点か終わり括弧が2文字以上」 全体に 「thx_closing_mark」 を付与して半角送りとし、 「thx_clps_spc」 の半角スペースを追加します。
句読点が1文字のみ
elseif ( $match[2] ) { // 句読点が1文字のみは、 ぶら下がり
return
'<span class = "thx_punc_wrap">' .
'<span class = "thx_punctuation">' .
$match[2] .
'</span>' .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
「句読点が1文字のみ」 が検出された場合は、 従来通りにぶら下がり処理を行います。
終わり括弧が1文字のみ
else { // ぶら下がり不要
return
'<span class = "thx_closing_mark">' .
$match[3] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
「終わり括弧が1文字のみ」 が検出された場合は、 半角送りを行うクラスとして 「thx_closing_mark」 を付与し、 「thx_clps_spc」 の半角スペースを追加します。
始め括弧
// 始め括弧の検索
'#' .
'[ ({[〔「『【〈《]+' .
'#uis' => function ( $match ) {
return
'<span class = "thx_clps_spc"> </span>' .
'<span class = "thx_opening_mark">' .
$match[0] .
'</span>';
},
特に変更はありませんが、 終わり括弧類のクラス名を 「thx_closing_mark」 と変更したので、 こちらも併せて 「thx_opening_mark」 と変更します。
css
/* 括弧など */
.thx_opening_mark,
.thx_closing_mark {
font-feature-settings: "halt";
}
その他の調整
その他にも細々とした調整を施していますので、 ざっと概略のみ紹介しておきます。
ゼロ幅スペース
例えば、 「。」 ではなく、 「。 」 と詰めずに表記したいケースもあるかもしれません。 それを実現するためにゼロ幅スペースを採り入れました。 詰めずに表記したい箇所に 「。 ​」 とゼロ幅スペースを挿入する事により、 “。” と “」” が連続しなくなるため、 「thx_clps_spc」 が挿入されます。
しかし、 ゼロ幅とは言えスペースに変わりはないので、 改行可能位置となる事に注意が必要です。 若しくは、 その性質を利用し、 改行しても良い箇所に敢えて挿入しておくのもアリかもしれません。
相殺スペースに隣接する和欧間スペース
相殺スペースは二分幅の半角スペース、 和欧間スペースは四分幅の半角スペースと、 どちらも実態は半角スペースなので単一化されるのですが、 二分幅なのか四分幅なのか表示結果が曖昧になるので、 隣接する際は和欧間スペースを除去します。
括弧内に隣接する和欧間スペース
和欧間スペースも実態は半角スペースなので改行可能位置です。 つまり、 括弧の禁則処理が効かなくなるケースがありますので、 括弧内に隣接する和欧間スペースを除去します。
文字種の追加
例の文書に 2.1.1 日本語組版に使用する文字なる項目がありましたので、 役物の文字種を追加します。
<code> 内では詰めを解除
全角役物が半角幅で表示されてしまい、 誤認識の恐れがあるのでこれを解除します。
ソースコード
php
// 簡易的な日本語組版
function thx_typesetting( $the_content ) {
//html をテキストとタグに分解
$pairing = html_split_text_tag( $the_content );
// ペアリングを span しながら結合
$the_content = '';
foreach ( $pairing as $value ) {
$str = trim( $value[0] );
$tag = $value[1];
if (
( '</style>' === $tag )
||
( '</rt>' === $tag )
||
( '</li>' === $tag )
) {
$the_content .= $str;
} else {
$the_content
.= preg_replace_callback_array(
[
// 欧文の検索 (ゼロスペースを含む場合は2文字以上)
'#' .
'[ !-;=-~\p{Ll}\x{200b}]{2,}' .
'|' .
'[ !-;=-~\p{Ll}]+' .
'#uis' => function ( $match ) {
return
'<span class = "thx_wao_spc"> </span>' .
'<span class = "thx_pwid">' .
$match[0] .
'</span>' .
'<span class = "thx_wao_spc"> </span>';
},
// 句読点と終わり括弧の検索 (後端単数の句読点のみ、 ぶら下がり)
'#' .
'([、。,.’”)〕]}〉》」』】 ]{2,})' .
'|' .
'([、。,. ])' .
'|' .
'([’”)〕]}〉》」』】 ])' .
'#uis' => function ( $match ) {
// 句読点や終わり括弧が2文字以上続いている場合
if ( $match[1] ) {
// 後端の1文字が句読点で、 後端の2文字が同一でなければ、 ぶら下がり処理
if (
(
' 、 ' === mb_substr( $match[1], -1 )
||
' 。 ' === mb_substr( $match[1], -1 )
||
' , ' === mb_substr( $match[1], -1 )
||
' . ' === mb_substr( $match[1], -1 )
)
&&
( mb_substr( $match[1], -1, 1 ) !== mb_substr( $match[1], -2, 1 ) )
) {
return
'<span class = "thx_closing_mark">' .
mb_substr( $match[1], 0, -1 ) .
'</span>' .
'<span class = "thx_punc_wrap">' .
'<span class = "thx_punctuation">' .
mb_substr( $match[1], -1 ) .
'</span>' .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
} else { // ぶら下がり不要
return
'<span class = "thx_closing_mark">' .
$match[1] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
} elseif ( $match[2] ) { // 句読点が1文字のみは、 ぶら下がり
return
'<span class = "thx_punc_wrap">' .
'<span class = "thx_punctuation">' .
$match[2] .
'</span>' .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
} else { // ぶら下がり不要
return
'<span class = "thx_closing_mark">' .
$match[3] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
}
},
// 中点の検索
'#' .
'[ ・:; ]+' .
'#uis' => function ( $match ) {
return
'<span class = "thx_clps_spc"> </span>' .
'<span class = "thx_mid_dot">' .
$match[0] .
'</span>' .
'<span class = "thx_clps_spc"> </span>';
},
// 始め括弧の検索
'#' .
'[ ‘“(〔[{〈《「『【]+' .
'#uis' => function ( $match ) {
return
'<span class = "thx_clps_spc"> </span>' .
'<span class = "thx_opening_mark">' .
$match[0] .
'</span>';
},
// 括弧内の和欧間スペースを除去 (禁則対策)
'#' .
'(<span class = "thx_opening_mark">[ ‘“(〔[{〈《「『【]+</span>)' .
'(<span class = "thx_wao_spc"> </span>)' .
'#uis' => function ( $match ) {
return $match[1];
},
'#' .
'(<span class = "thx_wao_spc"> </span>)' .
'(<span class = "thx_closing_mark">[、。,.’”)〕]}〉》」』】 ]+</span>)' .
'#uis' => function ( $match ) {
return $match[2];
},
// 欧文と役物以外を全角処理に
'#' .
'[^ !-~\p{Ll}、。,. ・:; ‘“(〔[{〈《「『【’”)〕]}〉》」』】 ]{2,}' .
'|' .
'[^ !-~\p{Ll}\x{200b}、。,. ・:; ‘“(〔[{〈《「『【’”)〕]}〉》」』】 ]+' .
'#uis' => function ( $match ) {
return '<span class = "thx_fwid">' . $match[0] . '</span>';
},
// ゼロスペース処理
'#' .
'[\x{200b}]+' .
'#uis' => function ( $match ) {
return '<span class = "thx_zero_spc">' . $match[0] . '</span>';
},
],
$str
);//$the_content .= preg_replace_callback_array()
}//else ( '</style>' === $tag )
$the_content .= $tag;
}//foreach ( $pairing as $value )
// 重複する thx_clps_spc を削除
$the_content
= str_replace(
'<span class = "thx_clps_spc"> </span><span class = "thx_clps_spc"> </span>',
'<span class = "thx_clps_spc"> </span>',
$the_content
);
// 相殺スペースは和欧間スペースを吸収
$the_content
= str_replace(
array(
'<span class = "thx_clps_spc"> </span><span class = "thx_wao_spc"> </span>',
'<span class = "thx_wao_spc"> </span><span class = "thx_clps_spc"> </span>',
),
'<span class = "thx_clps_spc"> </span>',
$the_content
);
// 括弧内の <a> などを禁則対策
$the_content
= preg_replace_callback_array(
[
'#' .
'(<span class = "thx_opening_mark">[ ‘“(〔[{〈《「『【]+</span>)' .
'(<[^>]*>)' .
'(<span class = "thx_wao_spc"> </span>)' .
'#uis' => function ( $match ) {
return $match[1] . $match[2];
},
'#' .
'(<span class = "thx_wao_spc"> </span>)' .
'(<[^>]*>)' .
'(<span class = "thx_closing_mark">[、。,.’”)〕]}〉》」』】 ]+</span>)' .
'#uis' => function ( $match ) {
return $match[2] . $match[3];
},
],
$the_content
);
return $the_content;
}//thx_typesetting( $the_content )
css
/* 禁則処理と両端揃え */
body {
line-break: strict;
text-align: justify;
}
/* 全角化 */
.thx_fwid {
font-feature-settings: "fwid";
}
/* プロポーショナル化 */
.thx_pwid {
font-feature-settings: "pwid";
}
/* ゼロ幅スペース (​ 、 \x{200b}) */
.thx_zero_spc {
-webkit-user-select: none;
user-select: none;
}
/* 和欧間スペース */
.thx_wao_spc {
font-family: Kosugi;
font-size: 0.5em;
line-height: 0;
-webkit-user-select: none;
user-select: none;
}
code .thx_wao_spc {
display: none;
}
/* 相殺スペース */
.thx_clps_spc {
-webkit-user-select: none;
user-select: none;
}
.chrome .thx_clps_spc {
font-family: sans-serif;
font-feature-settings: "hwid";
}
.gecko .thx_clps_spc {
font-family: Kosugi;
line-height: 0;
}
.safari .thx_clps_spc {
font-family: Kosugi;
line-height: 0;
}
body.amp .thx_clps_spc {
font-family: initial;
}
code .thx_clps_spc {
display: none;
}
/* 句読点 */
.thx_punc_wrap {
position: relative;
display: inline-block;
}
.thx_punc_punc {
font-feature-settings: "halt";
}
.thx_punc_clbr {
font-feature-settings: "halt";
}
.chrome .thx_punc_wrap + .thx_clps_spc {
font-feature-settings: "fwid";
}
.gecko .thx_punc_wrap + .thx_clps_spc {
font-size: 2em;
}
.safari .thx_punc_wrap + .thx_clps_spc {
font-size: 2em;
}
.thx_punctuation {
position: absolute;
font-feature-settings: "halt";
line-height: 1em;
}
code .thx_punc_wrap .thx_punctuation {
position: relative;
font-feature-settings: "fwid";
bottom: initial;
}
.chrome .thx_punctuation {
bottom: -0.1em;
}
.gecko .thx_punctuation {
bottom: -0.15em;
}
.safari .thx_punctuation {
bottom: -0.15em;
}
/* 中黒 */
:not(code)>.thx_mid_dot {
font-feature-settings: "halt";
}
:not(code)>.thx_clps_spc + .thx_mid_dot {
margin-left: -0.25em;
}
:not(code)>.thx_mid_dot + .thx_clps_spc {
margin-left: -0.25em;
}
/* 括弧など */
:not(code)>.thx_opening_mark,
:not(code)>.thx_closing_mark {
font-feature-settings: "halt";
}
まとめ
うん。 簡易的とは言え、 やりたかった事の殆どが実装できました。
また、 w3.org の文書を知り得た事が大きいです。 いや、 本来ならね、 こーゆーのは最初に調べておく事なんでしょうけど、 在るとは思わなかったので調べようともせず、 経験とカンと好みのみで進めてきました w
この文書のルールに近づけていけば、 いつの日か 「簡易的な」 の冠を外す事も出来そうですね。 おそらくしませんが、、、 まぁ、 クリティカルな問題があったり、 カンタンに実装できそうな項目があれば参照していきたいと思います。
続いては、 このシリーズ 「WordPress で InDesign の様な文字組」 の ④ 〜 ⑧ を汎用プラグインとしてまとめ、 ① 〜 ③ を Cocoon 用のプラグインとしてまとめる予定です。
コメント