m17n library でテキスト描画

(2005.5.15 新規作成)

m17n libraryは、オープンソース (LGPL 2.1) の、多言語ライブラリ。2018年2月にリリースされた m17n library v1.8.0 が最新。

メジャーなところでは, Emacs が m17n ライブラリを利用している。Emacs 27 から, m17n-flt の HarfBuzz への置き換えが始まっている。

以下サンプルのコンパイルには, Fedora 33 だと, m17n-lib-devel パッケージをインストールする。

タイ語とクメール文字を表示してみる。ついでに絵文字なども。

C++
[RAW]
  1. const uint16_t data[] = {
  2. 0x3042, // あ
  3. 0x4e9c, // 亜
  4. 0x0E19, 0x0E49, 0x0E33,
  5. 0x1787,
  6. 0x17BC,
  7. 0x1793, // Khmer Letter No
  8. 0xD83D, 0xDE01, // U+1F601 😁
  9. 0x0041,
  10. 0x845b, 0xdb40, 0xdd00, // U+E0100, // 葛󠄀 IVS. m17n は未対応.
  11. 0x845b, 0xdb40, 0xdd01, // U+E0101, // 葛󠄁 IVS
  12. 0x0031,
  13. 0};

main() 関数。

m17nライブラリを使う場合、最初にライブラリを初期化する。m17nライブラリでエラーが発生した場合は、グローバル変数 merror_code が設定される。

C++
[RAW]
  1. int main(int argc, char* argv[])
  2. {
  3. Arg args[1];
  4. Widget toplevel, canvas;
  5. XtSetLanguageProc(NULL, NULL, NULL);
  6. XtSetArg(args[0], XtNtitle, "Unicodeテキストの表示");
  7. toplevel = XtAppInitialize(&app, "view", NULL, 0, &argc, argv, NULL, args,
  8. 1);
  9. // m17nライブラリを初期化する。
  10. M17N_INIT();
  11. if (merror_code != MERROR_NONE) {
  12. printf("Fail to initialize the m17n library.\n");
  13. return 1;
  14. }

m17nでは、スクリプトごとにフォントを用意して、それらでフォントセットを作る。スクリプトは、例えば次のものがある。Unicode Character Database (UCD) の Scripts.txt の名前を使う。

スクリプトの例
スクリプト説明
hiraganaひらがな. 3041..3096, 309D..309E, ほか
katakanaカタカナ。半角カタカナを含む。30A1..30FA, ほか
han漢字。互換漢字を含む。

スクリプトごとのデフォルトフォント, そのスクリプトを含む文字集合 (CCS) との対応は, /usr/share/m17n/default.fst ファイル (m17n-db-extras パッケージ) を見るといい。

ここでは、タイ語用のフォントと日本語用のフォントを用意する。フォントを生成するには、フォント名を指定する方法 mfont_parse_name() と、mfont_put_prop()でファミリー名などフォントの要素を指定する方法がある。

C++
[RAW]
  1. // フォントセットを修正する
  2. MFont* thai_font = mfont_parse_name("Droid Sans", Mfontconfig);
  3. MFont* ja_font = mfont_parse_name("Noto Serif CJK JP", Mfontconfig);
  4. if (!thai_font || !ja_font) {
  5. printf("Fail to create font.\n");
  6. return 1;
  7. }
  8. // mfont_put_prop(thai_font, Mfamily, msymbol("norasi")); 違うフォントになる。
  9. MFontset* fs = mfontset_copy(mfontset(NULL), "my_fontset");
  10. mfontset_modify_entry(fs, msymbol("thai"), Mnil, Mnil,
  11. thai_font, msymbol("thai-norasi"), 0);
  12. mfontset_modify_entry(fs, msymbol("hiragana"), Mnil, Mnil,
  13. ja_font, Mnil, 0);
  14. mfontset_modify_entry(fs, msymbol("han"), Mnil, Mnil,
  15. ja_font, Mnil, 0);

フォントが生成できたら、フォントセットを修正する。mfontset_copy() でデフォルトのフォントセットをコピーして、それを修正するのがいいと思う。

mfontset_modify_entry() は、ヘルプの説明(引数の順序)が間違っている。正しくは次のとおり。scriptとlayouter_nameが重要。

int mfontset_modify_entry (
       MFontset *fontset,
       MSymbol script, 
       MSymbol language, 
       MSymbol charset,
       MFont *spec, 
       MSymbol layouter_name,
       int how);

m17nライブラリでのテキストは, M-text と呼ぶ。

C++
[RAW]
  1. // MFrameオブジェクトは、グラフィックデバイスに対応する。
  2. MPlist* param = mplist();
  3. MFace* face = mface();
  4. mface_put_prop(face, Mfontset, fs);
  5. mface_put_prop(face, Msize, (void*) 450);
  6. m17n_object_unref(fs); fs = nullptr;
  7. mplist_put(param, Mwidget, toplevel);
  8. mplist_put(param, Mface, face);
  9. m17n_object_unref(face); face = nullptr;
  10. frame = mframe(param);
  11. m17n_object_unref(param); param = nullptr;
  12. // M-textを生成する
  13. mt = mtext_from_data(data, 18, // code unit の個数 (code point ではない)
  14. MTEXT_FORMAT_UTF_16LE);
  15. if (!mt) {
  16. printf("Fail to create M-text object.\n");
  17. return 1;
  18. }
  19. canvas = XtVaCreateManagedWidget("canvas", formWidgetClass, toplevel,
  20. XtNwidth, 500,
  21. XtNheight, 200, NULL);
  22. XtAddEventHandler(canvas, ExposureMask, False, on_exposed, NULL);
  23. XtRealizeWidget(toplevel);
  24. // ウィンドウを閉じる. realize の後ろに書くこと。
  25. Atom atom = XInternAtom(XtDisplay(toplevel), "WM_DELETE_WINDOW", False);
  26. XSetWMProtocols(XtDisplay(toplevel), XtWindow(toplevel), &atom, 1);
  27. XtAddEventHandler(toplevel, NoEventMask, True, onCloseClicked, NULL);
  28. XtAppMainLoop(app);
  29. mfont_close(thai_font); // m17n_object_unref(thai_font) だと core dump.
  30. mfont_close(ja_font);
  31. m17n_object_unref(frame);
  32. m17n_object_unref(mt);
  33. M17N_FINI();
  34. printf("Finished.\n");
  35. return 0;
  36. }

イベントハンドラを用意。

C++
[RAW]
  1. #include <stdint.h>
  2. #include <X11/Intrinsic.h>
  3. #include <X11/StringDefs.h>
  4. #include <X11/Shell.h>
  5. #include <X11/Xaw/Form.h>
  6. #include <m17n-gui.h>
  7. #include <m17n-misc.h>
  8. MFrame* frame;
  9. MText* mt;
  10. XtAppContext app;
  11. static void on_exposed(Widget w, void* closure, XEvent* event, Boolean* cont)
  12. {
  13. int r = mdraw_image_text(frame, (MDrawWindow) XtWindow(w),
  14. 30, 100, mt, 0, 100);
  15. if (r != MERROR_NONE) {
  16. printf("%d\n", merror_code);
  17. exit(1);
  18. }
  19. }
  20. static void onCloseClicked(Widget w, void* closure, XEvent* event,
  21. Boolean* cont)
  22. {
  23. printf("onCloseClicked() enter.\n");
  24. // イベントループから抜ける.
  25. XtAppSetExitFlag(app);
  26. }

これをコンパイルして、実行すると、次のようになる。

gcc -Wall m17n.cc `pkg-config --cflags --libs m17n-core m17n-gui` -lXaw -lXt -lX11 -lstdc++

IVS には対応していない。絵文字は OK. Fedora 33 で実行すると, サロゲートペアがある場合に, 豆腐が発生する。UTF-16 の処理が不味いようだが, 回避方法はは不明。