torutkのブログ

ソフトウェア・エンジニアのブログ

Scene Builder 16 リリースと日本語文字化け解消

JavaFX GUIデザインツール Scene Builder 16リリース

JavaのモダンなGUIライブラリ JavaFX のデザインツール Scene Builder 16が3月末にリリースされました。

gluonhq.com

Scene Builder は、JavaFX GUIライブラリの画面レイアウトを作成するツールです。ドラッグ&ドロップで部品を配置しプレビュー表示が可能です。Javaのコードを書く必要がなく、設定はXMLファイルとして生成されます。

元々はOracleJavaFXの支援ツールとして開発していましたが、オープンソース化され、現在はGluon社が開発をサポートしています。

日本語環境での文字化け問題

Scene Builder のバージョン10の頃から、日本語環境で実行すると文字化けが発生する問題が生じました。

github.com

Scene Builderのメニューなどで日本語表示されるべき文字が、別の記号等に文字化けしています。

たとえば、メニュー項目の「ファイル」が、「ファイル」と本来とは別の文字・記号で表示されています。

文字化けの原因の推察

リリースされたScene Builderに含まれるリソースバンドルの日本語プロパティファイルを調べると、native2asciiでユニコードエスケープされています。このプロパティファイルの中でメニュー項目の「ファイル」の「フ」に該当する箇所のユニコードエスケープ表記を見ると、「\u00e3\u0192\u2022」となっています。文字にすると「フ」になります。本来「フ」に該当するユニコードエスケープ表記は「\u30d5」です。

Scene Builderのソースコードリポジトリに登録されている日本語プロパティファイルはUTF-8符号化で記述されています。ユニコードエスケープ(native2ascii)されてはいません。ということは、ビルドの過程でUTF-8符号化のプロパティファイルがユニコードスケープ(native2ascii)され、その際に文字化けが生じているものと推察しました。

文字化けが生じるビルド過程

Scene BuilderのビルドにはGradleツールが使われています。 Gradleのビルド定義(app/build.gradleファイル)を調べるとプロパティファイルを処理している個所は次の通りです。

processResources {
    def buildDate = new Date().format(buildDateFormat)
    from ('src/main/resources') {
        include '**/*.properties'
        expand([
            version: version,
            javaVersion: System.getProperty('java.runtime.version') + ', ' + System.getProperty('java.vendor'),
            buildDate: buildDate
        ])
        filter(EscapeUnicode)
    }
    into buildDir
}

この中で、次の記述がユニコードエスケープを実施している個所です。

filter(EscapeUnicode)

文字化けの再現に挑戦

問題の解決にはまず事象の再現が基本となります。そこで、この文字化けの再現を試みます。 Windows 10 日本語版の上で Scene Builder のソースコード一式を展開し、gradleでビルドしたところ、日本語プロパティファイルは文字化けとなったものの、リリースされているScene Builderの文字化けとは異なるものとなりました。

「ファイル」はユニコードエスケープでは次のコードに化けています。

\u7e5d\u8f14\u3043\u7e67\uff64\u7e5d\uff6b

文字にすると次です。

繝輔ぃ繧、繝ォ

ということで、単純には再現できませんでした。再現には、Gradleの振る舞いを理解する必要があります。

UTF-8SJISにまつわる文字化けでは糸編の漢字が多く表示 ネット上で見かけた情報で、UTF-8符号化されたバイトコードSJISとして解釈すると、糸編の漢字が登場しやすいとありました。上述の文字化けでも確かに化けて表示された7文字のうち3文字が糸編の漢字です。これは、「ファイル」をUTF-8符号化したバイナリコードを、SJISとして解釈した時の文字に一致しました。どうやら、Gradleが日本語プロパティファイルをSJIS(正確にはWindows-31j)として解釈してユニコードエスケープした結果生じた文字化けです。

Gradle の filter(EscapeUnicode)

Gradleの振る舞いを調べていくと、EscapeUnicodeによるフィルタは、入力ファイルをデフォルトエンコーディングとして解釈しユニコードエスケープするとありました。

そのため、Windows 10日本語環境で実行したときに、UTF-8SJISと誤って解釈した結果となりました。

では、Scene Builderのリリース版に含まれる文字化けはどのように発生するのでしょうか? ここで、Windows OSの英語版でビルドしていると仮定し、Windows OS英語版のデフォルトエンコーディングが何かを調べました。すると、コードページ1252 (ラテン文字1)でした。

Windows OS英語版でScene Builderをビルドすると、EscapeUnicodeによるフィルタは、入力ファイルをCP1252(Windows-1252)として解釈します。例えば、「フ」はUTF-8符号化すると「e3 83 95」となります。これをCP1252として解釈すると、「フ」となり、ユニコードエスケープすると「\u00e3\u0192\u2022」となります。

なるほど、事象が再現しそうです。

問題再現の環境設定

問題再現のために、Windows 10日本語版でデフォルトのエンコーディングをCP1252に変更します。 Windowsの「設定」 > 「時刻と言語」 > 「地域」 で、地域設定を「英語(米国)」に変更します。

Javaで認識するデフォルトエンコーディングを確認します。

D:\work> jshell
|  JShellへようこそ -- バージョン15.0.1
|  概要については、次を入力してください: /help intro

jshell> System.out.println(System.getProperty("file.encoding"))
Cp1252

変更できたので、Scene Builderのソースをビルドします。 結果、文字化けを再現することができました。

問題の再現に3年を費やした 問題が発生したのは2018年に Scene Builder 10がリリースされた時点です。このときは、同じ文字化けを再現することができませんでした。再現はできずとも回避策は提示できたのですが、回避策は環境変数を設定するもので、gradlew.batに設定を記載するPull Requestを作りましたが「gradle側の問題」として受け入れられませんでした。 Gradleをちょっと分かるようになった後から考えると、gradlew.batは gradleのバージョンアップ時に更新されるので、ここに修正を入れるのは不適切だったなぁと・・・

問題の解決

問題の原因は、GradleのEscapeUnicodeフィルタが入力のプロパティファイルをデフォルトエンコーディングで扱うため、UTF-8以外のデフォルトエンコーディングを持つ環境でビルドすると文字化けが発生するというものです。

UTF-8以外のデフォルトエンコーディングを持つ環境の代表例がWindows OSです。

解決の方針

解決案を列挙します。それぞれ検討・検証していきます。

  1. ユニコードエスケープをさせない
  2. UTF-8でEscapeUnicodeさせる
  3. ソースコードリポジトリに登録する日本語プロパティファイルを予めユニコードエスケープ(native2ascii)させる
解決案1

Javaは、JDK 9からリソースバンドル・プロパティファイルをUTF-8で扱えるようになりました(それ以前はISO 8859-1)。そこで、ユニコードエスケープをやめればうまくいくのではというのが解決案1のアプローチです。

次の記述を削除します。

filter(EscapeUnicode)

しかしながら、この結果は不調でした。日本語プロパティファイルはユニコードエスケープされなくなったものの、文字化けです。これは、Gradleがリソースディレクトリのファイルをコピーする際に文字列の解釈が入ってしまい、UTF-8以外のデフォルトエンコーディング環境で文字化けるというものと思い割れます。

解決案2

GradleでEscapeUnicodeの処理をUTF-8として扱うよう設定を追加するアプローチです。 ただし、環境変数での設定やGradlegが生成するファイルの修正ではなく、Scene Builder側で管理するファイルでの設定を追求します。

調査結果、次の記述をbuild.gradleに追加します。

filteringCharset = 'UTF-8'

これはうまくいきました。

解決案3

Scene Builderには、リソースバンドルのプロパティファイルが3つ登録されています。デフォルトの英語プロパティ、中国語プロパティ、そして日本語プロパティです。

英語および中国語のプロパティファイルは、ユニコードエスケープされたファイルがソースコードリポジトリに登録されています。

では、日本語もユニコードエスケープしてしまえというのが解決案3です。

しかし、プロパティファイルはUTF-8で扱えるのがJava SE 9以降であり、しかもnative2asciiコマンドがJDK 10から削除されているご時勢です。ユニコードエスケープしたファイルを扱うのはとても苦痛ですから、このアプローチは最後の手段として取っておくこととします。

解決の提案

解決案2がうまくいったので、これをPRとして提案したところ、採用されました(2021.2)。

よかったよかった。

最後に雑記

経緯は次のチケット(個人Redmine)に記載しています。 www.torutk.com

  • 「きっとすぐに、誰かが修正するに違いない」と思っていたが、年単位が経過しても修正されずにきてしまった。
  • 環境に起因する問題は再現が厄介、とくにロケールに絡む問題は。Javaのデフォルトエンコーディングは厄介。
  • Gradleも中の動きが分かりにくい