C++ で TeX画像を作成

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

(2020 年 9 月 6 日)インストーラー追加,ヘルプファイル一部修正

目次

サンプル・プログラム

圧縮ファイルの内容
mimetexwin.msiインストーラ
bin/mimetexwin.exe実行プログラム本体
bin/etc/minetex.exemimeTeX実行プログラム
bin/etc/help.chmヘルプ・ファイル
sour/mimetexwin.cppソース・プログラム
sour/resource.hリソース・ヘッダ
sour/resource.rcリソース・ファイル
sour/application.icoアプリケーション・アイコン

mineTeX の入手

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" に設定する。

解説:ヘッダファイル等

0010: // 初期化処理 ======================================================
0011: #include <iostream>
0012: #include <cstdio>
0013: #include <string>
0014: #include <sstream>
0015: #include <windows.h>
0016: #include <commctrl.h>
0017: #include <gdiplus.h>
0018: #include <tchar.h>
0019: #include <stdio.h>
0020: #include <stdlib.h>
0021: #include <ole2.h>
0022: #include <richedit.h>
0023: #include <boost/format.hpp>
0024: #include "resource.h"
0025: 
0026: using namespace std;
0027: using namespace Gdiplus;
0028: using namespace boost;
0029: 
0030: #define APPNAME     "TEX画像ファイル作成"    //アプリケーション名
0031: #define APPVERSION "1.1"                    //バージョン
0032: #define APPYEAR     "2020"                    //作成年
0033: #define REFERENCE "https://www.pahoo.org/e-soul/webtech/cpp01/cpp01-07-01.shtm"  // 参考サイト
0034: 
0035: //char*バッファサイズ
0036: #define SIZE_BUFF     512
0037: 
0038: //標準フォント
0039: #define FONT_FACE     "MS UI Gothic"
0040: 
0041: //現在のインターフェイス
0042: static HINSTANCE hInst;
0043: 
0044: //親ウィンドウ
0045: static HWND hParent;
0046: 
0047: //エラー・メッセージ格納用
0048: string ErrorMessage;
0049: 
0050: //ヘルプ・ファイル
0051: #define HELPFILE ".\\etc\\help.chm"
0052: 
0053: //mimeTex.exe
0054: #define MIMETEX     ".\\etc\\mimetex.exe"
0055: 
0056: //TeX文の初期値
0057: #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" を利用する。

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

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

0229: /**
0230:  * テンポラリファイル名を取得(Windows API使用+拡張子.gif)
0231:  * @param char* tpmname  テンポラリファイル名を格納(フルパス)
0232:  * @return なし
0233: */
0234: void getTempFname(chartmpname) {
0235:     char szTempPath[MAX_PATH + 1];
0236: 
0237:     GetTempPath(sizeof(szTempPath) / sizeof(szTempPath[0]), szTempPath);
0238:     GetTempFileName(szTempPath, "mimetex", 0, tmpname);
0239:     remove(tmpname);     //ごみファイル削除
0240:     strcat(tmpname, ".gif");
0241: }

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

解説:mimeTeX実行

0243: /**
0244:  * mimeTeX実行(system関数使用)
0245:  * @param const char *tex     TeX文
0246:  * @param const char *tpmname 一時出力するgifファイル名
0247:  * @return なし
0248: */
0249: void makeTeX(const char *texconst char *tmpname) {
0250:     char cmd[SIZE_BUFF + 1];
0251:     for (int i = 0; i < SIZE_BUFF + 1; i++)      cmd[i] = 0;
0252: 
0253:     strcat(cmdMIMETEX);
0254:     strcat(cmd, " -e ");
0255:     strcat(cmdtmpname);
0256:     strcat(cmd,  " \"");
0257:     strcat(cmdtex);
0258:     strcat(cmd, "\"");
0259:     system(cmd);
0260: }

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

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

0262: /**
0263:  * 画像ファイルの表示
0264:  * @param HWND  hWnd 表示するウィンドウ・ハンドル
0265:  * @param const char *tpmname 表示するgifファイル名
0266:  * @return なし
0267: */
0268: void onPaint(HWND hWndconst char *tmpname) {
0269:     WCHAR szFileW[SIZE_BUFF + 1];
0270:     IStream *pStream;
0271: 
0272:     TCHAR *szFile = TEXT((char *)tmpname);
0273:     MultiByteToWideChar(932, 0, szFile,-1, szFileWsizeof(szFileW) / sizeof(TCHAR));
0274:     Graphics mygraphics(hWnd);
0275:     //表示領域クリア
0276:     mygraphics.Clear(Gdiplus::Color(255, 255, 255, 255));
0277:     Bitmap *image;
0278:     //ストリーム読み込み準備
0279:     HANDLE hFile = CreateFile(szFileGENERIC_READ, 0, NULLOPEN_ALWAYSFILE_ATTRIBUTE_NORMALNULL);
0280:     if (!hFile)      return;
0281:     DWORD dwFileSize = GetFileSize(hFileNULL);
0282:     HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLEdwFileSize);
0283:     //ロックしてポインタ取得
0284:     LPVOID lpBuf = GlobalLock(hGlobal);
0285:     //メモリ読み込み
0286:     ReadFile(hFilelpBufdwFileSize, &dwFileSizeNULL);
0287:     CloseHandle(hFile);
0288:     //アンロック
0289:     GlobalUnlock(hGlobal);
0290:     //ストリーム変換
0291:     CreateStreamOnHGlobal(hGlobalTRUE, &pStream);
0292:     //ストリームから読み込み
0293:     image = Bitmap::FromStream(pStream);
0294:     //画面表示
0295:     mygraphics.DrawImage(image, 10, 10);
0296:     //メモリ解放
0297:     GlobalFree(hGlobal);
0298: }

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

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

0300: /**
0301:  * 画像ファイルの保存
0302:  * @param const char *tpmname 一時ファイル名
0303:  * @return なし
0304: */
0305: void saveTeX(const chartmpname) {
0306:     static char fname[MAX_PATH + 1];
0307:     strcat(fname, "mimetex.gif");
0308:     OPENFILENAME of;
0309: 
0310:     //OPENFILENAME構造体のサイズをセット
0311:     memset(&of, 0, sizeof(OPENFILENAME));
0312:     of.lStructSize = sizeof(OPENFILENAME);
0313:     //ダイアログボックスを所有するウィンドウへのハンドル
0314:     of.hwndOwner = hParent;
0315:     of.lpstrFilter = TEXT("*.gif\0*.gif\0\0");
0316:     //ファイル名を格納したバッファのアドレス
0317:     of.lpstrFile = (LPTSTR)fname
0318:     //lpstrFileメンバで指定されるバッファのサイズ
0319:     of.nMaxFile = MAX_PATH;
0320:     of.Flags = OFN_OVERWRITEPROMPT;
0321:     //デフォルトの拡張子を格納したバッファのアドレス
0322:     of.lpstrDefExt = TEXT("gif");
0323:     //コモンダイアログの表示
0324:     GetSaveFileName(&of);
0325:     copyFile(tmpnamefname);
0326: }

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

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

0145: /**
0146:  * イベントハンドラ:バージョン表示ダイアログ
0147:  * @param HWND hDlg   ウィンドウ・ハンドラ
0148:  * @paramm UINT uMsg
0149:  * @param WPARAM wParam
0150:  * @paramL PARAM lParam
0151:  * @return INT_PTR CALLBACK
0152: */
0153: INT_PTR CALLBACK processHelp(HWND hDlgUINT uMsgWPARAM wParamLPARAM lParam) {
0154:     string help;
0155: 
0156:     switch(uMsg){
0157:         //ダイアログ初期化
0158:         case WM_INITDIALOG:
0159:             CenterWindow(hDlg);
0160:             //メッセージ作成
0161:             help = (boost::format(R"(
0162: %1%  バージョン %2%
0163: Copyright by (c)studio pahoo, %3%
0164: 
0165: 本アプリケーションはMIT Licenseです.
0166: 商用を含む無償利用が可能です.自由に改造できます.
0167: 再配布の際は,下記の著作権表記,およびURLと本使用条件を必ず明記してください.
0168: 
0169: Copyright by (c)studio pahoo, %3%
0170: https://www.pahoo.org/
0171: なお,本アプリケーションの利用または改造することによって生じた得失ついては一切関知いたしません.APIの変更・停止によって正常に機能しなくなる場合があります.また,二次利用先の組織・企業・団体の目的・内容・活動については一切関知いたしません.
0172: 
0173: )
")
0174: APPNAME
0175: APPVERSION
0176: APPYEAR
0177: ).str();
0178:             setStrEditBox(hDlgIDC_TEXT_HELPhelp);
0179:             break;
0180: 
0181:         //ボタン押下
0182:         case WM_COMMAND:
0183:              switch (LOWORD(wParam)) {
0184:                 //実行
0185:                 case IDC_BUTTON_OK:
0186:                     EndDialog(hDlg, 0);
0187:                     break;
0188:                 default:
0189:                     return 1;
0190:             }
0191:             break;
0192:         //プログラム終了
0193:         case WM_CLOSE:
0194:             EndDialog(hDlg, 0);
0195:             break;
0196:     }
0197:     return 0;
0198: }

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

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

0342: /**
0343:  * イベントハンドラ:メインウィンドウ
0344:  * @param HWND hDlg           親ウィンドウ・ハンドラ
0345:  * @paramm UINT uMsg           メッセージ識別子
0346:  * @param WPARAM wParam       メッセージの最初のパラメータ
0347:  * @paramL PARAM lParam       メッセージの2番目のパラメータ
0348:  * @return INT_PTR CALLBACK   TRUE:メッセージ処理完了/FALSE:未完了
0349: */
0350: INT_PTR CALLBACK processMain(HWND hDlgUINT uMsgWPARAM wParamLPARAM lParam) {
0351:     HICON hIcon;
0352:     GdiplusStartupInput gpSI;
0353:     ULONG_PTR lpToken;
0354:     static HWND hTex;
0355:     static char tmpname[MAX_PATH + 1];
0356:     char *str;
0357: 
0358:     switch(uMsg){
0359:         //ダイアログ初期化
0360:         case WM_INITDIALOG:
0361:             hParent = hDlg;
0362:             hIcon = (HICON)LoadImage(hInstMAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 16, 16, 0);
0363:             SendMessage(hParentWM_SETICONICON_SMALL, (LPARAM)hIcon);
0364:             setFont(GetDlgItem(hParentIDC_EDIT_TEX), 16, FONT_FACE);
0365:             setStrEditBox(hParentIDC_EDIT_TEXTEX_DEF);
0366:             getTempFname(tmpname);
0367:             //GDI+初期化
0368:             GdiplusStartup(&lpToken, &gpSINULL);
0369:             break;
0370: 
0371:         //ボタン押下
0372:         case WM_COMMAND:
0373:              switch (LOWORD(wParam)) {
0374:                 //実行
0375:                 case IDC_BUTTON_EXEC:
0376:                 case IDM_EXEC:
0377:                     makeTeX(getStrEditBox(hParentIDC_EDIT_TEX).c_str(), tmpname);
0378:                     hTex = GetDlgItem(hParentIDC_IMAGE_TEX);
0379:                     onPaint(hTextmpname);
0380:                     break;
0381:                 //保存
0382:                 case IDC_BUTTON_SAVE:
0383:                 case IDM_SAVE:
0384:                     saveTeX(tmpname);
0385:                     break;
0386:                 //ヘルプ
0387:                 case IDM_HELP:
0388:                     ShellExecute(hParent_T("open"), _T(HELPFILE), NULLNULLSW_RESTORE);
0389:                     break;
0390:                 //バージョン表示
0391:                 case IDM_VERSION:
0392:                     createHelp(hParentprocessHelp);
0393:                     break;
0394:                 //解説サイト
0395:                 case IDM_PAHOO:
0396:                     ShellExecute(NULL_T("open"), _T(REFERENCE), NULLNULLSW_RESTORE);
0397:                     break;
0398:                 //コピー
0399:                 case IDM_COPY:
0400:                     setClipboardData(getStrEditBox(hParentIDC_EDIT_TEX));
0401:                     break;
0402:                 //貼り付け
0403:                 case IDM_PASTE:
0404:                     str = getClipboardData();
0405:                     if (str != NULL) {
0406:                         setStrEditBox(hParentIDC_EDIT_TEXstr);
0407:                     }
0408:                     break;
0409:                 //切り取り
0410:                 case IDM_DELETE:
0411:                     setStrEditBox(hParentIDC_EDIT_TEX, "");
0412:                     break;
0413:                 //プログラム終了
0414:                 case IDM_QUIT:
0415:                     remove(tmpname);
0416:                     //GDI+終了
0417:                     GdiplusShutdown(lpToken);
0418:                     EndDialog(hParent, 0);
0419:                     return 0;
0420:                 default:
0421:                     return 1;
0422:             }
0423:             break;
0424:         //プログラム終了
0425:         case WM_CLOSE:
0426:             remove(tmpname);
0427:             //GDI+終了
0428:             GdiplusShutdown(lpToken);
0429:             EndDialog(hParent, 0);
0430:             return 0;
0431:     }
0432:     return 0;
0433: }

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

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

0436: /**
0437:  * Windowsメインプログラム
0438:  * @param HINSTANCE hInstance           インスタンスハンドル
0439:  * @paramm HINSTANCE hPrevInstance       未使用(常にNULL):Win16時代の名残
0440:  * @param LPSTR lpCmdLine               コマンドライン引数
0441:  * @paramL int nShowCmd               ウィンドウの表示方法
0442:  * @return int リターンコード
0443: */
0444: int WINAPI WinMain(HINSTANCE hInstanceHINSTANCE hPrevInstanceLPSTR lpCmdLineint nShowCmd) {
0445:     LoadLibrary("RICHED20.DLL");
0446:     hInst = hInstance;
0447:     DialogBox(hInstanceMAKEINTRESOURCE(IDD_MAIN), NULL, (DLGPROC)processMain);
0448: 
0449:     return 0;
0450: }

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

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

参考サイト

(この項おわり)
header