JFreeChart でグラフをレンダリングするときに、グラフのカテゴリ ラベルに日本語の文字が含まれていると、レイアウトの問題が発生することに気付きました。テキストは正しいグリフでレンダリングされますが、おそらくフォント メトリックが間違っていたため、テキストが間違った場所に配置されていました。
グラフは、もともとそのテキストにSource Sans Pro Regularフォントを使用するように構成されており、ラテン文字セットのみをサポートしています。明らかな解決策は、実際の日本語の .TTF フォントをバンドルして、JFreeChart に使用を依頼することです。出力テキストが正しいグリフを使用し、正しくレイアウトされているという点で、これはうまく機能します。
私の質問
最初のシナリオで、ラテン文字以外を実際にサポートしていないソース フォントを使用している場合、java.awt はどのようにして日本語の文字を正しくレンダリングしたのでしょうか? 問題があれば、JDK 1.7u45 を使用して OS X 10.9 でテストしています。
別の日本語フォントをバンドルせずに日本語の文字をレンダリングする方法はありますか? (これが私の最終目標です!) バンドル ソリューションは機能しますが、回避できるのであれば、アプリケーションに 6 MB の肥大化を追加したくありません。Java は、フォントがなくても (少なくとも私のローカル環境では) どうにかして日本語のグリフをレンダリングする方法を明確に知っています。これが以下の「frankenfont」の問題に関連しているかどうか疑問に思っています。
JRE が内部変換を実行した後、なぜ Source Sans Pro フォントは ( canDisplayUpTo()を介して) 呼び出し元に、日本語の文字を表示できないのに表示できると通知するのでしょうか? (下記参照。)
明確にするために編集:
これはサーバー アプリであり、レンダリングしているテキストはクライアントのブラウザーや PDF エクスポートに表示されます。グラフは常にサーバー上で PNG にラスタライズされます。
サーバーの OS や環境を制御することはできません。Java 標準のプラットフォーム フォントを使用するのは良いことですが、多くのプラットフォームではフォントの選択が貧弱であり、私のユース ケースでは受け入れられないため、独自のフォントをバンドルする必要があります (少なくともラテン フォントの場合)。日本語テキストにプラットフォーム フォントを使用してもかまいません。
アプリは、テキスト タイプの先験的な知識がなくても、日本語とラテン語のテキストを組み合わせて表示するように求められる可能性があります。グリフが正しくレンダリングされる限り、文字列に言語が混在している場合にどのフォントが使用されるかについて、私は曖昧です。
詳細
java.awt.Font#TextLayout はスマートであり、テキストをレイアウトしようとすると、最初に下層のフォントに提供された文字を実際にレンダリングできるかどうかを問い合わせることを理解しています。そうでない場合は、おそらくそれらの文字をレンダリングする方法を知っている別のフォントにスワップしますが、JRE クラスにかなり深くまでデバッグしたことに基づいて、ここでは発生していません。TextLayout#singleFont
常にフォントの null 以外の値を返し、fastInit()
コンストラクターの一部を処理します。
非常に興味深い点の 1 つは、Source Sans Pro フォントが、JRE がフォントで変換を実行した後、日本語の文字をレンダリングする方法を知っていることを呼び出し元に強制的に伝えることです。
例えば:
// We load our font here (download from the first link above in the question)
File fontFile = new File("/tmp/source-sans-pro.regular.ttf");
Font font = Font.createFont(Font.TRUETYPE_FONT, new FileInputStream(fontFile));
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
// Here is some Japanese text that we want to display
String str = "クローズ";
// Should say that the font cannot display any of these characters (return code = 0)
System.out.println("Font " + font.getName() + " can display up to: " + font.canDisplayUpTo(str));
// But after doing this magic manipulation, the font claims that it can display the
// entire string (return code = -1)
AttributedString as = new AttributedString(str, font.getAttributes());
Map<AttributedCharacterIterator.Attribute,Object> attributes = as.getIterator().getAttributes();
Font newFont = Font.getFont(attributes);
// Eeek, -1!
System.out.println("Font " + newFont.getName() + " can display up to: " + newFont.canDisplayUpTo(str));
これの出力は次のとおりです。
Font Source Sans Pro can display up to: 0
Font Source Sans Pro can display up to: -1
上記の「魔法の操作」の 3 行は、私自身が行ったものではないことに注意してください。真のソース フォント オブジェクトを JFreeChart に渡しますが、グリフを描画するときに JRE によって変更されます。これは、上記の「魔法の操作」コードの 3 行が複製するものです。上記の操作は、次の一連の呼び出しで発生するものと機能的に同等です。
- org.jfree.text.TextUtilities#drawRotatedString
- sun.java2d.SunGraphics2D#drawString
- java.awt.font.TextLayout#(コンストラクタ)
- java.awt.font.TextLayout#singleFont
「魔法の」操作の最後の行で Font.getFont() を呼び出すと、Source Sans Pro フォントが返されますが、基になるフォントのfont2D
フィールドは元のフォントとは異なり、この単一のフォントは認識していると主張します。文字列全体をレンダリングする方法。なんで?Java は、基礎となるソース フォントで提供されるグリフのメトリックしか理解できないにもかかわらず、あらゆる種類のグリフをレンダリングする方法を知っているある種の「frankenfont」を私たちに返しているようです。
JFreeChart のレンダリング例を示すより完全な例は、JFreeChart の例の 1 つに基づいています: https://gist.github.com/sdudley/b710fd384e495e7f1439この例の出力を以下に示します。