C++ で TeX画像を作成

(1/1)
C++でTeX画像を作成
PHPでTeXを表示する」で作ったTeX表示プログラムをC++に移植する。作成したTeXはGIF画像ファイルとして保存することができる。

(2024年3月6日)使用ライブラリ更新
(2023年10月14日)使用ライブラリ更新
(2023年6月3日)使用ライブラリ更新

目次

サンプル・プログラム

圧縮ファイルの内容
mimetexwin.msiインストーラ
bin/mimetexwin.exe実行プログラム本体
bin/etc/mimetex.exemimeTeX実行プログラム
bin/etc/help.chmヘルプ・ファイル
sour/mimetexwin.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル
sour/application.icoアプリケーション・アイコン
sour/makefileビルド
mimetexwin.cpp 更新履歴
バージョン 更新日 内容
1.5.4 2024/03/06 使用ライブラリ更新
1.2.3 2023/10/14 使用ライブラリ更新
1.2.2 2023/06/03 使用ライブラリ更新
1.2.1 2023/02/26 使用ライブラリ更新
1.2.0 2022/11/03 ウィンドウ位置・検索キー保存,使用ライブラリ更新

mimeTeX の入手

mimeTeX は、LaTeX のサブセットを画像表示するためのソフトウェアである。GPLライセンスで配布されている。(Copyright © 2002-2012, John Forkosh Associates, Inc.)
今回は、Windows用実行プログラム mimetex.exe を子プロセスで起動させ、GIFファイルを生成する方式を採った。

使用ライブラリ

今回は、オープンソースのライブラリ Boost C++ライブラリを使用する。導入方法等については、「C++ 開発環境の準備」をご覧いただきたい。

GIFファイルを扱うために、Windowsの GDI+ を利用する。
ライブラリなど必要はファイルは "\Windows\System32" に導入されているはずなので、追加で用意するものはない。

リソースの準備

Eclipse を起動し、新規プロジェクト mimetexwin を用意する。
ResEdit を起動し、resource.rc を用意する。
Eclipse に戻り、ソース・プログラム "mimetexwin.cpp" を追加する。
今回は GDI+ を使用する関係で、リンカー・フラグを -mwindows -static -lstdc++ -lgcc -lwinpthread -lgdi32 -lgdiplus "C:\Windows\system32\GdiPlus.dll" "C:\Windows\System32\ole32.dll" に設定する。

MSYS2 コマンドラインからビルドするのであれば、"makefile" を利用してほしい。

解説:ヘッダファイル等

  10: // 初期化処理 ======================================================
  11: #include <iostream>
  12: #include <cstdio>
  13: #include <string>
  14: #include <sstream>
  15: #include <winsock2.h>
  16: #include <windows.h>
  17: #include <shlobj.h>
  18: #include <commctrl.h>
  19: #include <gdiplus.h>
  20: #include <tchar.h>
  21: #include <stdio.h>
  22: #include <stdlib.h>
  23: #include <ole2.h>
  24: #include <richedit.h>
  25: #include <boost/property_tree/xml_parser.hpp>
  26: #include <boost/format.hpp>
  27: #include "resource.h"
  28: 
  29: using namespace std;
  30: using namespace Gdiplus;
  31: using namespace boost;
  32: using namespace boost::property_tree;
  33: 
  34: #define MAKER       "pahoo.org"             //作成者
  35: #define APPNAME     "mimetexwin"            //アプリケーション名
  36: #define APPNAMEJP   "TEX画像ファイル作成"   //アプリケーション名(日本語)
  37: #define APPVERSION  "1.2.4"                 //バージョン
  38: #define APPYEAR     "2020-2024"             //作成年
  39: #define REFERENCE   "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-07-01.shtm"   // 参考サイト
  40: 
  41: //char*バッファサイズ
  42: #define SIZE_BUFF       5120
  43: 
  44: //標準フォント
  45: #define FONT_FACE       "MS UI Gothic"
  46: 
  47: //現在のインターフェイス
  48: static HINSTANCE hInst;
  49: 
  50: //アプリケーション・ウィンドウ
  51: HWND hParent;
  52: 
  53: //アプリケーション・ウィンドウ位置
  54: unsigned hParent_X, hParent_Y;
  55: 
  56: //エラー・メッセージ格納用
  57: string ErrorMessage;
  58: 
  59: //TeX格納用
  60: string TeX;
  61: 
  62: //ヘルプ・ファイル
  63: #define HELPFILE    ".\\etc\\help.chm"
  64: 
  65: //mimeTex.exe
  66: #define MIMETEX     ".\\etc\\mimetex.exe"
  67: 
  68: //TeX文の初期値
  69: #define TEX_DEF     "\\large f^\\prime(x)\\ = \\lim_{\\Delta x\\to0}\\frac{f(x+\\Delta x)-f(x)}{\\Delta x}"

C++の 生文字リテラルに変数を表示できるように、Boost C++ライブラリから "format.hpp" を利用する。

その他の定数は、自由に変更できる。

解説:テンポラリファイル名を取得

 489: /**
 490:  * テンポラリファイル名を取得(Windows API使用+拡張子.gif)
 491:  * @param   char* tpmname  テンポラリファイル名を格納(フルパス)
 492:  * @return  なし
 493: */
 494: void getTempFname(char* tmpname) {
 495:     char szTempPath[MAX_PATH + 1];
 496: 
 497:     GetTempPath(sizeof(szTempPath) / sizeof(szTempPath[0]), szTempPath);
 498:     GetTempFileName(szTempPath, "mimetex", 0, tmpname);
 499:     remove(tmpname);        //ごみファイル削除
 500:     strcat(tmpname, ".gif");
 501: }

mimeTeX が生成するGIFファイルを一時保存するためのファイル名を、Windows APIを使って作成する。

解説:mimeTeX実行

 503: /**
 504:  * mimeTeX実行(system関数使用)
 505:  * @param   const char *tex     TeX文
 506:  * @param   const char *tpmname 一時出力するgifファイル名
 507:  * @return  なし
 508: */
 509: void makeTeX(const char *tex, const char *tmpname) {
 510:     char cmd[SIZE_BUFF + 1];
 511:     for (int i = 0i < SIZE_BUFF + 1i++)      cmd[i] = 0;
 512: 
 513:     strcat(cmd, MIMETEX);
 514:     strcat(cmd, " -e ");
 515:     strcat(cmd, tmpname);
 516:     strcat(cmd,  " \"");
 517:     strcat(cmd, tex);
 518:     strcat(cmd, "\"");
 519:     system(cmd);
 520: }

mimeTeX は system関数を使って実行する。

解説:画像ファイルの表示

 522: /**
 523:  * 画像ファイルの表示
 524:  * @param   HWND  hWnd 表示するウィンドウ・ハンドル
 525:  * @param   const char *tpmname 表示するgifファイル名
 526:  * @return  なし
 527: */
 528: void onPaint(HWND hWnd, const char *tmpname) {
 529:     WCHAR szFileW[SIZE_BUFF + 1];
 530:     IStream *pStream;
 531: 
 532:     TCHAR *szFile = TEXT((char *)tmpname);
 533:     MultiByteToWideChar(932, 0, szFile,-1, szFileW, sizeof(szFileW) / sizeof(TCHAR));
 534:     Graphics mygraphics(hWnd);
 535:     //表示領域クリア
 536:     mygraphics.Clear(Gdiplus::Color(255, 255, 255, 255));
 537:     Bitmap *image;
 538:     //ストリーム読み込み準備
 539:     HANDLE hFile = CreateFile(szFile, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
 540:     if (!hFile)     return;
 541:     DWORD dwFileSize = GetFileSize(hFile, NULL);
 542:     HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize);
 543:     //ロックしてポインタ取得
 544:     LPVOID lpBuf = GlobalLock(hGlobal);
 545:     //メモリ読み込み
 546:     ReadFile(hFile, lpBuf, dwFileSize, &dwFileSize, NULL);
 547:     CloseHandle(hFile);
 548:     //アンロック
 549:     GlobalUnlock(hGlobal);
 550:     //ストリーム変換
 551:     CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
 552:     //ストリームから読み込み
 553:     image = Bitmap::FromStream(pStream);
 554:     //画面表示
 555:     mygraphics.DrawImage(image, 10, 10);
 556:     //メモリ解放
 557:     GlobalFree(hGlobal);
 558: }

mimeTeX が生成したGIFファイルを表示するためのユーザー関数が onPaint である。
ここで、画像ファイルを Bitmap::FromFile メソッドで読み込もうとすると、本アプリが終了するまでファイル・ハンドルを握って離さないため、テンポラリファイルが削除できなくなってしまう。
そこで面倒ではあるが、テンポラリファイルをメモリに読み込んで、ストリームに変換。Bitmap::FromStream メソッドで画像を読み込んで表示するようにした。

解説:画像ファイルの保存

 560: /**
 561:  * 画像ファイルの保存
 562:  * @param   const char *tpmname 一時ファイル名
 563:  * @return  なし
 564: */
 565: void saveTeX(const char* tmpname) {
 566:     static char fname[MAX_PATH + 1];
 567:     strcat(fname, "mimetex.gif");
 568:     OPENFILENAME of;
 569: 
 570:     //OPENFILENAME構造体のサイズをセット
 571:     memset(&of, 0, sizeof(OPENFILENAME));
 572:     of.lStructSize = sizeof(OPENFILENAME);
 573:     //ダイアログボックスを所有するウィンドウへのハンドル
 574:     of.hwndOwner = hParent;
 575:     of.lpstrFilter = TEXT("*.gif\0*.gif\0\0");
 576:     //ファイル名を格納したバッファのアドレス
 577:     of.lpstrFile = (LPTSTR)fname
 578:     //lpstrFileメンバで指定されるバッファのサイズ
 579:     of.nMaxFile = MAX_PATH;
 580:     of.Flags = OFN_OVERWRITEPROMPT;
 581:     //デフォルトの拡張子を格納したバッファのアドレス
 582:     of.lpstrDefExt = TEXT("gif");
 583:     //コモンダイアログの表示
 584:     GetSaveFileName(&of);
 585:     copyFile(tmpname, fname);
 586: }

mimeTeX が生成したGIFファイルをコピーして保存する。保存ファイル名を指定するためにファイル・ダイアログを表示するようにした。

解説:イベントハンドラ:バージョン表示ダイアログ

 423: /**
 424:  * イベントハンドラ:バージョン表示ダイアログ
 425:  * @param   HWND hDlg           ウィンドウ・ハンドラ
 426:  * @paramm  UINT uMsg           メッセージ識別子
 427:  * @param   WPARAM wParam       メッセージの最初のパラメータ
 428:  * @paramL  PARAM lParam        メッセージの2番目のパラメータ
 429:  * @return  INT_PTR CALLBACK    TRUE:メッセージ処理完了/FALSE:未完了
 430:  */
 431: INT_PTR CALLBACK processHelp(HWND hDlg, UINT uMsg,
 432:                     WPARAM wParam, LPARAM lParam) {
 433:     switch (uMsg) {
 434:     //ダイアログ初期化
 435:     case WM_INITDIALOG:
 436:         CenterWindow(hDlg);
 437:         setStrEditBox(hDlg, IDC_TEXT_HELP, Version);
 438:         break;
 439: 
 440:     //ボタン押下
 441:     case WM_COMMAND:
 442:         switch (LOWORD(wParam)) {
 443:         //実行
 444:         case IDC_BUTTON_OK:
 445:             EndDialog(hDlg, 0);
 446:             break;
 447:         default:
 448:             return 1;
 449:         }
 450:         break;
 451:     //プログラム終了
 452:     case WM_CLOSE:
 453:         EndDialog(hDlg, 0);
 454:         break;
 455:     }
 456:     return 0;
 457: }

バージョン情報をモーダル・ダイアログで表示させる。このダイアログに関わるイベント・ハンドラが processHelp である。
表示メッセージはC++の 生文字リテラルを使っているが、ここで変数を利用できるように、Boost C++ライブラリから "format.hpp" を利用した。

解説:イベントハンドラ:メインウィンドウ

 603: /**
 604:  * イベントハンドラ:メインウィンドウ
 605:  * @param   HWND hDlg           親ウィンドウ・ハンドラ
 606:  * @paramm  UINT uMsg           メッセージ識別子
 607:  * @param   WPARAM wParam       メッセージの最初のパラメータ
 608:  * @paramL  PARAM lParam        メッセージの2番目のパラメータ
 609:  * @return  INT_PTR CALLBACK    TRUE:メッセージ処理完了/FALSE:未完了
 610: */
 611: INT_PTR CALLBACK processMain(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
 612:     HICON hIcon;
 613:     GdiplusStartupInput gpSI;
 614:     ULONG_PTR lpToken;
 615:     static HWND hTex;
 616:     static char tmpname[MAX_PATH + 1];
 617:     char *str;
 618: 
 619:     switch(uMsg){
 620:     //ダイアログ初期化
 621:     case WM_INITDIALOG:
 622:         hParent = hDlg;
 623:         hIcon = (HICON)LoadImage(hInst, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
 624:         SendMessage(hParent, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
 625:         setFont(GetDlgItem(hParent, IDC_EDIT_TEX), 16, FONT_FACE);
 626:         //オプション読み込み
 627:         loadParameter();
 628:         setStrEditBox(hDlg, IDC_EDIT_TEX, TeX);
 629:         //アプリケーション・ウィンドウ移動
 630:         SetWindowPos(hParent, NULL, hParent_X, hParent_Y, 0, 0, (SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER));
 631:         getTempFname(tmpname);
 632:         //GDI+初期化
 633:         GdiplusStartup(&lpToken, &gpSI, NULL);
 634:         break;
 635: 
 636:     //ボタン押下
 637:     case WM_COMMAND:
 638:         switch (LOWORD(wParam)) {
 639:         //実行
 640:         case IDC_BUTTON_EXEC:
 641:         case IDM_EXEC:
 642:             TeX = getStrEditBox(hParent, IDC_EDIT_TEX);
 643:             makeTeX(TeX.c_str(), tmpname);
 644:             hTex = GetDlgItem(hParent, IDC_IMAGE_TEX);
 645:             onPaint(hTex, tmpname);
 646:             break;
 647:         //保存
 648:         case IDC_BUTTON_SAVE:
 649:         case IDM_SAVE:
 650:             saveTeX(tmpname);
 651:             break;
 652:         //設定クリア+アプリ終了
 653:         case IDM_CLEAR_PARAMETER:
 654:             delParameter();
 655:             //テンポラリ・ファイル削除
 656:             remove(tmpname);
 657:             EndDialog(hParent, 0);
 658:             return 0;
 659:             break;
 660:         //ヘルプ
 661:         case IDM_HELP:
 662:             ShellExecute(hParent, _T("open"), _T(HELPFILE), NULL, NULL, SW_RESTORE);
 663:             break;
 664:         //バージョン表示
 665:         case IDM_VERSION:
 666:             createHelp(hParent, processHelp);
 667:             break;
 668:         //解説サイト
 669:         case IDM_PAHOO:
 670:             ShellExecute(NULL, _T("open"), _T(REFERENCE), NULL, NULL, SW_RESTORE);
 671:             break;
 672:         //コピー
 673:         case IDM_COPY:
 674:             setClipboardData(getStrEditBox(hParent, IDC_EDIT_TEX));
 675:             break;
 676:         //貼り付け
 677:         case IDM_PASTE:
 678:             str = getClipboardData();
 679:             if (str !NULL) {
 680:                 setStrEditBox(hParent, IDC_EDIT_TEX, str);
 681:             }
 682:             break;
 683:         //切り取り
 684:         case IDM_DELETE:
 685:             setStrEditBox(hParent, IDC_EDIT_TEX, "");
 686:             break;
 687:         //プログラム終了
 688:         case IDM_QUIT:
 689:             //オプション保存
 690:             saveParameter();
 691:             remove(tmpname);
 692:             //GDI+終了
 693:             GdiplusShutdown(lpToken);
 694:             EndDialog(hParent, 0);
 695:             return 0;
 696:         default:
 697:             return 1;
 698:         }
 699:         break;
 700:     //プログラム終了
 701:     case WM_CLOSE:
 702:         //オプション保存
 703:         saveParameter();
 704:         remove(tmpname);
 705:         //GDI+終了
 706:         GdiplusShutdown(lpToken);
 707:         EndDialog(hParent, 0);
 708:         return 0;
 709:     }
 710:     return 0;
 711: }

メインウィンドウに関わるイベント・ハンドラが processMain である。
ダイアログ初期化の際に GDI+ を初期化し、プログラム終了時に GDI+ を終了する。
いくつかのボタンとメニューは共通の機能を担っている。

解説:Windowsメインプログラム

 714: /**
 715:  * Windowsメインプログラム
 716:  * @param   HINSTANCE hInstance         インスタンスハンドル
 717:  * @paramm  HINSTANCE hPrevInstance     未使用(常にNULL):Win16時代の名残
 718:  * @param   LPSTR lpCmdLine             コマンドライン引数
 719:  * @paramL  int nShowCmd                ウィンドウの表示方法
 720:  * @return  int リターンコード
 721: */
 722: int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) {
 723:     LoadLibrary("RICHED20.DLL");
 724:     hInst = hInstance;
 725:     DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
 726: 
 727:     return 0;
 728: }

TeX 式を入力するエディットボックスは、コントロールキーが使えるように RichEdit にしてある。
プログラムの冒頭で "RICHED20.DLL" を読み込んでやる必要がある。

その他の関数、ヘルプファイルやインストーラー作成方法については、これまでの連載で説明してきたとおりである。

参考サイト

(この項おわり)
header