MEP14: テキスト処理#
ステータス番号
討論
ブランチとプルリクエスト#
問題 #253 は、テキストのアドバンス幅ではなくバウンディング ボックスを使用すると、テキストの位置がずれるというバグを示しています。これは大まかな計画のマイナー ポイントですが、この MEP の一部として対処する必要があります。
アブストラクト#
テキストの処理方法を再編成することにより、この MEP は次のことを目指しています。
Unicode および非 ltr 言語のサポートを改善する
テキストのレイアウトを改善する (特に複数行のテキスト)
より多くのフォント、特に非 Apple 形式の TrueType フォントと OpenType フォントをサポートできるようにします。
フォント構成をより簡単かつ透明にする
詳細な説明#
テキストのレイアウト
現在、matplotlib にはテキストをレンダリングする 2 つの異なる方法があります。「組み込み」(FreeType と独自の Python コードに基づく) と「usetex」(TeX インストールの呼び出しに基づく) です。「ビルトイン」レンダラーの補助として、TeX をインストールせずに TeX 言語のサブセットを使用して数式をレンダリングするための Python ベースの「mathtext」システムもあります。これらの 2 つのエンジンのサポートは、すべてのバックエンドを含む多くのソース ファイルに散らばっています。
if rcParams['text.usetex']: # do one thing else: # do another
3 番目のテキスト レンダリング アプローチ (詳細は後述) を追加するには、これらすべての場所も編集する必要があるため、スケーリングされません。
代わりに、この MEP は「テキスト エンジン」の概念を追加することを提案しています。この概念では、ユーザーはテキストをレンダリングするためのさまざまなアプローチから 1 つを選択できます。これらのそれぞれの実装は、独自のモジュール セットにローカライズされ、ソース ツリー全体に小さな部分はありません。
テキスト レンダリング エンジンを追加する理由 「組み込み」のテキスト レンダリングには多くの欠点があります。
右から左に記述する言語のみを処理し、ダイアクリティカルの組み合わせなど、Unicode の多くの特殊機能は処理しません。
複数行のサポートは不完全で、手動の改行しかサポートしていません。段落を特定の長さの行に分割することはできません。
また、Markdown、reStructuredText、HTML などをサポートするためのインライン フォーマットの変更も処理しません。(この MEP ではリッチ テキスト フォーマットが検討されていますが、この設計でそれが可能であることを確認したいので、リッチ テキスト フォーマットの実装の詳細は、この MEP の範囲外です。)
これらのことをサポートするのは難しく、他の多くのプロジェクトの「フルタイムの仕事」です:
上記のオプションのうち、 harfbuzzは依存関係が最小限のクロスプラットフォーム オプションとして最初から設計されていることに注意してください。したがって、単一のオプションをサポートするのに適しています。
さらに、リッチ テキストをサポートするために、 WebKitの使用を検討することができます。繰り返しになりますが、リッチ テキスト形式はこのプロジェクトの範囲外です。
車輪を再発明してこれらの機能を matplotlib の「組み込み」テキスト レンダラーに追加しようとするのではなく、これらのプロジェクトを活用してより強力なテキスト レイアウトを取得する方法を提供する必要があります。「ビルトイン」レンダラーは、インストールを容易にするために引き続き存在する必要がありますが、その機能セットは他のレンダラーに比べてより制限されます。[TODO: この MEP は、これらの制限された機能が何であるかを明確に決定し、バグを修正して、動作させたいすべてのケースで実装が正しく動作する状態にする必要があります。私は@leejjoonがこれについていくつかの考えを持っていることを知っています.]
フォントの選択
フォントの抽象的な記述からディスク上のファイルへの移行は、フォント選択アルゴリズムのタスクです。最初に思われるよりもはるかに複雑であることが判明しました。
「ビルトイン」レンダラーと「usetex」レンダラーでは、テクノロジが異なるため、フォント選択の処理方法が大きく異なります。たとえば、TeX では TeX 固有のフォント パッケージをインストールする必要があり、TrueType フォントを直接使用することはできません。残念ながら、フォント選択のセマンティクスは異なりますが、それぞれに同じフォント プロパティのセットが使用されます。これは、
FontProperties
クラスとフォント関連rcParams
(基本的に同じコードを共有する) の両方に当てはまります。代わりに、すべてのテキスト エンジンで機能するフォント選択パラメーターのコア セットを定義し、必要に応じてユーザーがエンジン固有の操作を実行できるようにエンジン固有の構成を用意する必要があります。たとえば、
rcParams["font.family"]
(デフォルト:['sans-serif']
)、しかし「usetex」では同じことはできません。XeTeX を使用して TrueType フォントを使いやすくすることは可能かもしれませんが、ユーザーは TeX フォント パッケージを介して従来のメタフォントを使用したいと考えるでしょう。そのため、さまざまなテキスト エンジンにはエンジン固有の構成が必要であり、どの構成が複数のテキスト エンジンで機能し、どの構成がエンジン固有であるかがユーザーにとってより明確になるはずです。
「usetex」を除いても、フォントを見つけるにはさまざまな方法があることに注意してください。デフォルトでは、 CSS フォント マッチング アルゴリズムfont_manager
に基づく独自のアルゴリズムを使用してフォントを照合するフォント リスト キャッシュを使用します。Linux のネイティブ フォント選択アルゴリズム ( fontconfig ) と常に同じことをするとは限りません。)、Mac および Windows であり、OS が通常取得するシステム上のすべてのフォントが常に検出されるとは限りません。ただし、これはクロスプラットフォームであり、matplotlib に同梱されているフォントを常に見つけます。Cairo および MacOSX バックエンド (およびおそらく将来の HTML5 ベースのバックエンド) は現在、このメカニズムをバイパスし、OS ネイティブのものを使用します。SVG、PS、または PDF ファイルにフォントを埋め込まず、サードパーティのビューアーで開く場合も同様です。マイナス面は、(少なくとも Cairo では、MacOSX で確認する必要があります)、matplotlib に同梱されているフォントが常に見つかるとは限らないことです。(ただし、フォントを検索パスに追加することは可能かもしれません。または、OS がフォントを見つけると予想する場所にフォントをインストールする方法を見つける必要があるかもしれません)。
PS と PDF には、これらの形式で常に使用できるコア フォントのみを使用する特別なモードもあります。そこでは、フォント検索メカニズムはそれらのフォントに対してのみ一致する必要があります。OS ネイティブのフォント検索システムがこのケースを処理できるかどうかは不明です。
matplotlib のフォント選択にfontconfigを使用するための実験的なサポートもあり、デフォルトではオフになっています。fontconfig は Linux のネイティブ フォント選択アルゴリズムですが、クロス プラットフォームでもあり、他のプラットフォームでもうまく機能します (ただし、そこには明らかに追加の依存関係があります)。
上記で提案されたテキスト レイアウト ライブラリ (pango、QtTextLayout、DirectWrite、CoreText など) の多くは、独自のエコシステムからフォント選択ライブラリを使用することを主張しています。
上記のすべては、私たちが自分で書いたフォント選択アルゴリズムから離れて、可能な場合はネイティブ API を使用する必要があることを示唆しているようです。これは、Cairo と MacOSX のバックエンドがすでに使用したいと考えているものであり、複雑なテキスト レイアウト ライブラリの要件になります。Linux では、 fontconfig実装の骨組みがすでにあります (pango からもアクセスできます)。Windows と Mac では、カスタム ラッパーを作成する必要がある場合があります。良い点は、フォント検索用の API が比較的小さく、基本的に「フォント プロパティの辞書があれば、一致するフォント ファイルを提供する」ことで構成されていることです。
フォントのサブセット化
フォントのサブセット化は現在、ttconv を使用して処理されます。ttconv は、TrueType フォントをサブセット化された Type 3 フォント (他の機能の中でも特に) に変換するためのスタンドアロンのコマンドライン ユーティリティで、1995 年に作成されました。このユーティリティは、ライブラリとして機能させるために matplotlib (まあ、私) がフォークしました。Microsoft (または他のベンダー) エンコーディングのフォントではなく、Apple スタイルの TrueType フォントのみを処理します。OpenType フォントはまったく処理しません。つまり、STIX フォントは .otf ファイルとして提供されますが、matplotlib に同梱するには、それらを .ttf ファイルに変換する必要があります。Linux のパッケージ担当者はこれを嫌います。アップストリームの STIX フォントに依存するだけです。ttconv には、時間の経過とともに修正が困難ないくつかのバグがあることも示されています。
代わりに、FreeType を使用してフォントのアウトラインを取得し、(おそらく Python で) 独自のコードを記述して、サブセット化されたフォント (PS と PDF では Type 3、SVG ではパス) を出力できるはずです。Freetype は、よく管理されている人気のあるプロジェクトであり、さまざまなフォントを実際に扱っています。これにより、多くのカスタム C コードが削除され、バックエンド間のコードの重複が一部削除されます。
この方法でフォントをサブセット化することは、最も簡単な方法ですが、フォントのヒントが失われることに注意してください。そのため、現在行っているように、可能であればファイルにフォント全体を埋め込む方法を提供する必要があります。
代替のフォント サブセット オプションには、Cairo に組み込まれているサブセットの使用 (Cairo の残りの部分なしで使用できるかどうかは明らかではありません)、またはfontforgeの使用(これは重く、プラットフォーム間の依存関係はそれほど大きくありません) が含まれます。
フリータイプのラッパー
私たちの FreeType ラッパーは、実際に再加工を使用できます。独自の画像バッファ クラスを定義します (Numpy 配列の方が簡単な場合)。FreeType は非常に多様なフォント ファイルを処理できますが、Apple ベンダー以外の TrueType ファイルや OpenType ファイルの特定の機能をサポートするのが非常に難しくなるラッパーの制限があります。(Windows 7 および 8 に同梱されているフォントをサポートするためだけに、これのひどい結果については #2088 を参照してください)。このラッパーを新しく書き直すと、大いに役立つと思います。
テキストの固定と配置と回転
ベースラインの処理は 1.3.0 で変更され、テキストの下部ではなく、テキストのベースラインの位置がバックエンドに与えられるようになりました。これはおそらく正しい動作であり、MEP リファクタリングもこの規則に従う必要があります。
複数行のテキストの配置をサポートするには、(提案された) テキスト エンジンがテキストの配置を処理する必要があります。各エンジンは、特定のテキスト チャンクに対して、そのテキストの境界ボックスと、そのボックス内のアンカー ポイントのオフセットを計算します。したがって、ブロックの va が "top" の場合、アンカー ポイントはボックスの上部になります。
テキストの回転は、常にアンカー ポイントを中心に行う必要があります。それがmatplotlibの現在の動作と一致するかどうかはわかりませんが、それは最も健全な/最も驚くべき選択のようです. [何かが機能するようになったら、これを再検討することができます]。テキストの回転は、テキスト エンジンによって処理されるべきではありません。これは、テキスト エンジンとレンダリング バックエンドの間のレイヤーによって処理され、統一された方法で処理できるようにする必要があります。[ローテーションをテキスト エンジンで個別に処理するメリットはないと思います...]
この作業の一部として解決する必要がある、テキストの配置とアンカーに関する他の問題があります。[TODO: これらを列挙]。
修正すべきその他の軽微な問題
mathtext コードにはバックエンド固有のコードがあります。代わりに、別のテキスト エンジンとして出力を提供する必要があります。ただし、別のテキスト エンジンによって実行されるより大きなレイアウトの一部として、数学テキスト レイアウトを挿入することが依然として望ましいため、これを行うことができるはずです。任意のテキスト エンジンのテキスト レイアウトを別のエンジンに埋め込むことができるかどうかは未解決の問題です。
テキスト モードは現在、グローバル rcParam ("text.usetex") によって設定されているため、すべてオンまたはすべてオフです。テキスト エンジン ("text.layout_engine") を選択するためのグローバルな rcParam を引き続き持つ必要がありますが、Text
必要に応じて同じ図で複数のテキスト レイアウト エンジンの結果を組み合わせることができるように、オブジェクトのオーバーライド可能なプロパティにする必要があります。 .
実装#
「テキストエンジン」の概念を紹介します。各テキスト エンジンは、多数の抽象クラスを実装します。TextFont
インターフェイスは、指定された一連のフォント プロパティのテキストを表します。必ずしも 1 つのフォント ファイルに限定されるわけではありません。レイアウト エンジンがリッチ テキストをサポートしている場合、ファミリ内の多数のフォント ファイルを処理できます。インスタンスを指定TextFont
すると、ユーザーはインスタンスを取得できます。TextLayout
インスタンスは、特定のフォントで特定のテキスト文字列のレイアウトを表します。a からsTextLayout
に対するイテレータTextSpan
が返されるため、エンジンはできるだけ少ないスパンを使用して生の編集可能なテキストを出力できます。エンジンが個々の文字を取得したい場合は、TextSpan
インスタンスから取得できます。
class TextFont(TextFontBase):
def __init__(self, font_properties):
"""
Create a new object for rendering text using the given font properties.
"""
pass
def get_layout(self, s, ha, va):
"""
Get the TextLayout for the given string in the given font and
the horizontal (left, center, right) and verticalalignment (top,
center, baseline, bottom)
"""
pass
class TextLayout(TextLayoutBase):
def get_metrics(self):
"""
Return the bounding box of the layout, anchored at (0, 0).
"""
pass
def get_spans(self):
"""
Returns an iterator over the spans of different in the layout.
This is useful for backends that want to editable raw text as
individual lines. For rich text where the font may change,
each span of different font type will have its own span.
"""
pass
def get_image(self):
"""
Returns a rasterized image of the text. Useful for raster backends,
like Agg.
In all likelihood, this will be overridden in the backend, as it can
be created from get_layout(), but certain backends may want to
override it if their library provides it (as freetype does).
"""
pass
def get_rectangles(self):
"""
Returns an iterator over the filled black rectangles in the layout.
Used by TeX and mathtext for drawing, for example, fraction lines.
"""
pass
def get_path(self):
"""
Returns a single Path object of the entire laid out text.
[Not strictly necessary, but might be useful for textpath
functionality]
"""
pass
class TextSpan(TextSpanBase):
x, y # Position of the span -- relative to the text layout as a whole
# where (0, 0) is the anchor. y is the baseline of the span.
fontfile # The font file to use for the span
text # The text content of the span
def get_path(self):
pass # See TextLayout.get_path
def get_chars(self):
"""
Returns an iterator over the characters in the span.
"""
pass
class TextChar(TextCharBase):
x, y # Position of the character -- relative to the text layout as
# a whole, where (0, 0) is the anchor. y is in the baseline
# of the character.
codepoint # The unicode code point of the character -- only for informational
# purposes, since the mapping of codepoint to glyph_id may have been
# handled in a complex way by the layout engine. This is an int
# to avoid problems on narrow Unicode builds.
glyph_id # The index of the glyph within the font
fontfile # The font file to use for the char
def get_path(self):
"""
Get the path for the character.
"""
pass
フォントのサブセットを出力したいグラフィック バックエンドは、キーが (fontname, glyph_id) で、値がパスである文字のファイル グローバル ディクショナリを構築する可能性が高いため、各文字のパスのコピーが 1 つだけ保存されます。ファイル。
特別なケース: 「usetex」機能は現在、TeX から直接 Postscript を取得して Postscript ファイルに直接挿入することができますが、他のバックエンドでは、DVI ファイルを解析し、より抽象的なものを生成します。このような場合、はほとんどのバックエンドにTextLayout
実装
しますが、このメソッドの存在を探して利用可能であればそれを使用する Postscript バックエンドをget_spans
追加するか、 にフォールバックします。この種の特殊なケースは、グラフィックス バックエンドとテキスト エンジンが同じエコシステム (Cairo と Pango、または MacOSX と CoreText など) に属している場合などに必要になる場合もあります。get_ps
get_spans
実装には 3 つの主要部分があります。
freetype ラッパーを書き直し、ttconv を削除します。
(1) が完了したら、概念実証として、上流の STIX .otf フォントに移動できます。
リモート URL から読み込まれた Web フォントのサポートを追加します。(フォントのサブセット化に freetype を使用することで有効になります)。
既存の「組み込み」コードと「usetex」コードを別々のテキスト エンジンにリファクタリングし、上記の API に従うようにします。
高度なテキスト レイアウト ライブラリのサポートの実装。
(1) と (2) はかなり独立していますが、(1) を最初に実行すると (2) をより単純にすることができます。(3) は (1) と (2) に依存しますが、それができなくても (または延期されても)、(1) と (2) を完了することで「ビルトイン」の改善が進みやすくなります。テキストエンジン。
下位互換性#
アンカーと回転に関するテキストのレイアウトは、うまくいけば小さいながらも改善された方法で変更されます。水平方向の配置が考慮されるため、複数行のテキストのレイアウトが大幅に改善されます。双方向テキストのレイアウトまたはその他の高度な Unicode 機能は、本質的に機能するようになりました。これにより、ユーザーが現在独自の回避策を使用している場合、いくつかのことが壊れる可能性があります。
フォントは異なる方法で選択されます。"ビルトイン" と "usetex" のテキスト レンダリング エンジンの間で機能していたハックは、機能しなくなる可能性があります。以前は matplotlib によって検出されなかった、OS によって検出されたフォントが選択される場合があります。
代替案#
未定