Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

レイアウト桁位置に対するロジック桁位置の進め方を改善する #1658

Merged
4 commits merged into from May 5, 2021
Merged

Conversation

ghost
Copy link

@ghost ghost commented May 3, 2021

PR の目的

レイアウト情報の作成過程で、ロジック桁位置の進む量を文字に応じて変化させる。

カテゴリ

  • 仕様変更

PR の背景

現状のレイアウト作成処理では、レイアウト桁位置に対応するロジック桁を次の数だけ進めるようになっています。

  • ワードラップ: 単語の桁数
  • 行頭禁則: 禁則対象文字と追い出される文字を合わせた桁数
  • その他の禁則処理: 禁則対象文字の桁数
  • それ以外: 1文字分の桁数

ここでいう文字の桁数は、1文字が必ずロジック単位で1つであることを想定しているようです。
ロジック行上のデータはUTF-16で表現されるため、サロゲートペアはロジック単位で2つ進めなければなりません。
このため、レイアウト作成フローはサロゲートペアが分割されてしまうリスクを抱えています。

一方、行データのイテレータである CMemoryIterator クラスでは、
以前からロジック・レイアウト単位の増分をそれぞれ CNativeW クラスの静的メンバで計算しており、
サロゲートペアに遭遇しても問題がないようになっています。

PR のメリット

レイアウト作成時にサロゲートペアが前後に分割処理されることによって発生しうる不具合を防げます。

PR のデメリット (トレードオフとかあれば)

この PR の変更対象としたクラスには単体テストがなく、共有データ依存のため追加も難しいです。
このため一時的にカバレッジが低下します(全期間で見たカバレッジには影響ありません)。

仕様・動作説明

レイアウト作成処理において、現在処理している物理データ行上の文字の位置は、 SLayoutWork 構造体のメンバ変数に保持されています。
これまでこの変数にロジック単位で(文字ごとに)1ずつ加算していたところを、各文字毎にCNativeW::GetSizeOfChar()で取得した値を加算するように変更します。

なお、既存コードに追加した GetSizeOfChar のオーバーロードを活用できる箇所がいくつかありますが、遡及適用は行っていません。

PR の影響範囲

各行のレイアウト作成を行う際の準処理(CLayoutMgr::_MakeOneLine()

テスト内容

以下の手順で変更前後の動作に変更がないことを確認してください。

手順

指定した内容でタイプ別設定を変更したのち、添付したマクロ実行します。
テスト用マクロ (test.zip)

  1. タイプ別設定一覧ダイアログから、「テキスト」タイプの設定を次のように変更します。
    (この設定変更を忘れないでください。)
    • 英文ワードラップ:有効
    • 句読点ぶら下げ:有効
    • 改行ぶらさげ:有効
    • 行頭禁則:有効
    • 行末禁則:有効
  2. 添付のマクロを実行し、結果を確認します。
    • マクロで挿入されるデータは 329 文字・レイアウト 57 行・ロジック 29 27 行です。

関連 issue, PR

#1543

参考資料

@AppVeyorBot
Copy link

Build sakura 1.0.3728 completed (commit 2ec9b6627d by @kazasaku)

@beru beru added the specification change ■仕様変更 label May 4, 2021
Copy link
Member

@kengoide kengoide left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

概要を読んだ限りでは不適切な仕様を改善する変更のように思えますが、実際に現在の実装でサロゲートペアが分割される例があるのかどうかが分かりませんでした。

分割される例があるかどうかの検証が必要だと思います。実害がないなら変更そのものが不要になってしまいますし、あるのなら改善を証明するテスト手法があったほうが良いように思います。

//! 指定した位置の文字がwchar_t何個分かを返す
static CLogicInt GetSizeOfChar( const wchar_t* pData, int nDataLen, int nIdx );
static CLogicInt GetSizeOfChar( const CStringRef& cStr, int nIdx )
{ return GetSizeOfChar( cStr.GetPtr(), cStr.GetLength(), nIdx ); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

追加したオーバーロードを検証するテストが欲しいです。

Copy link
Author

@ghost ghost May 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CStringRef を扱う GetKetaOfChar / GetColmOfChar のオーバーロードにもテストが無いです。
これも追加しときます。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

対応完了。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CStringRef を扱う GetKetaOfChar / GetColmOfChar のオーバーロードにもテストが無いです。

これはたぶん私のやらかしです。追加していただきありがとうございます。

CLayoutInt k = cLayoutMgr.GetLayoutXOfChar(cLineStr, nPos);
if(0 == k)k = CLayoutInt(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これ、削除して大丈夫ですか? ゼロ幅スペースなどの幅0pxの文字でも1px描画するのが今の仕様であるように見えます。消してしまうと仕様変更になりませんか?

Copy link
Author

@ghost ghost May 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

現時点ではここに来るのは次の文字の時だけです( IS_KEYWORD_CHAR の仕様による)。
#$0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZ\_abcdefghijklmnopqrstuvwxyz

自分には過去に行ったサロゲートペア対応時の削除漏れに見えました。
また、この箇所以外に0桁を1桁にみなす動作をしている箇所はすべてコメントアウトされているはずです。
(このPRではCode Smellになってしまうので除去しています。)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

おっしゃる通り、IS_KEYWORD_CHAR の条件があるので不要ということでよさそうですね。了解しました。

@ghost
Copy link
Author

ghost commented May 4, 2021

不適切な仕様を改善する変更のように思えますが、実際に現在の実装でサロゲートペアが分割される例があるのかどうかが分かりません

実際に問題になったのは行頭禁則処理においてです。次行に追い出されるロジック単位文字数が足りませんでした。
#1543 では条件判定追加で済ませましたが、後から桁数の算出方法が非対称になっている(※)ことがそもそもの原因だと思いましたので、本PRを作成しました。
(※:レイアウト単位の増分は文字ごとに計算するのに対して、ロジック単位の増分は値が固定されている)

@AppVeyorBot
Copy link

Build sakura 1.0.3730 completed (commit 8be14e313d by @kazasaku)

// テスト用の文字列参照を提供するユーティリティ
class GetStringRef {
public:
CStringRef SetString(const wchar_t* pcStr, size_t nLen) { return CStringRef(pcStr, nLen); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetSizeOfChar の呼び出し時に CStringRef を直接構築すると不都合があるのでしょうか? 少し考えてみましたがよく分かりませんでした。

@brief CStringRef型文字列を使用したGetSizeOfCharの仕様確認
@remark 文字列中の指定位置にある文字の符号単位数を返す
*/
TEST(CNativeW, GetSizeOfChar_with_CStringRef) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

これは好みの問題かもしれませんが、テストケースごと増やすよりも以前からのテストケースに assertion を追加する形の方が簡潔で良いように思えます。同じコメントを繰り返す必要がなくなりますし、内容のほぼ同じ asssertion は隣接していた方が読む人のストレス軽減に役立ちそうです。

//! 指定した位置の文字がwchar_t何個分かを返す
static CLogicInt GetSizeOfChar( const wchar_t* pData, int nDataLen, int nIdx );
static CLogicInt GetSizeOfChar( const CStringRef& cStr, int nIdx )
{ return GetSizeOfChar( cStr.GetPtr(), cStr.GetLength(), nIdx ); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CStringRef を扱う GetKetaOfChar / GetColmOfChar のオーバーロードにもテストが無いです。

これはたぶん私のやらかしです。追加していただきありがとうございます。

@sonarcloud
Copy link

sonarcloud bot commented May 4, 2021

@AppVeyorBot
Copy link

Build sakura 1.0.3732 completed (commit 639d03ccf7 by @kazasaku)

@kengoide
Copy link
Member

kengoide commented May 4, 2021

添付のマクロを動かしてみました。

  • PR適用前後で結果に変化はありませんでした。
  • 「329 文字・レイアウト 57 行」でしたが、「ロジック 29 行」ではなく「27 行」になっているように見えます。(環境依存? ロジック行の数え方が間違っている?)

行番号を数えて29でしょうか? 終了時のステータスバーの表示であれば「28 行」になっています。

@ghost
Copy link
Author

ghost commented May 4, 2021

…!

「329 文字・レイアウト 57 行」でしたが、「ロジック 29 行」ではなく「27 行」になっているように見えます。

27 行で正しいです。失礼しました。

@kengoide
Copy link
Member

kengoide commented May 4, 2021

27 行で正しいです。

でしたら大丈夫ですね😃

実際に問題になったのは行頭禁則処理においてです。次行に追い出されるロジック単位文字数が足りませんでした。
#1543 では条件判定追加で済ませましたが、後から桁数の算出方法が非対称になっている(※)ことがそもそもの原因だと思いましたので、本PRを作成しました。

ある種の弥縫策を改めて本修正にしたという理解でよろしいでしょうか。そういうことでしたら前後で変化がないことを確認するテストで適切ですね。

Copy link
Member

@kengoide kengoide left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

良さそうに見えます。

@berryzplus
Copy link
Contributor

レビューありがとうございます。
このへんは難しくて、積極的にレビューに加われなかった感じですが、異論はないです。

サクラエディタの内部文字コードは、いわゆるUnicodeです。
Unicodeには複数のエンコーディング方式が存在していますが、Windows標準はUTF-16LEなので
サクラエディタの内部エンコーディングもUTF-16LEを基本としています。

形式 内容 備考
文字列 🚹 × 🚺
uint32_t[] \u1F6B9 \xD7 \u1F6BA
wchar_t[] \xD83D \xDEB9 \xD7 \xD83D \xDEBA サロゲートペアのhi,lowは必ずこの順。
std::byte[] \x3D \xD8 \xB9 \xDE \xD7 \x00 \x3D \xD8 \xBA \xDE リトルエンディアンなのでこう見える。

サクラエディタでは、Unicodeに変換できなかったバイトデータを \xDCxx にマップする(ことがある)という仕様があるので、末尾から前方に向けて一文字進めるときの処理が若干複雑になります。

@ghost
Copy link
Author

ghost commented May 5, 2021

レビューありがとうございます。
マージしてしまいます。
他に問題があれば対応いたしますのでお知らせください。

@ghost ghost merged commit 7206fa0 into sakura-editor:master May 5, 2021
@ghost ghost deleted the feature/change_layout_maker_use_getsizeofchar branch May 5, 2021 06:52
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
specification change ■仕様変更
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants