C++ 戻り値の異なる関数 template
先日からやっている .ini やレジストリにアクセスする関数.
MFC の CWinApp::GetProfileString , CWinApp::WriteProfileString にあたる部分は目途がついた.
それで,それらを呼出す部分.前に作成したものもそうだったが,型ごとにクラスの関数を定義していた.
これらをもう少し簡単にできないかと…
set の方は,特に難しい所はない(::To_tstring は,文字列に変換する関数として用意している).
template <class T> bool INI_set (LPCTSTR sec,LPCTSTR ent,const T& val)
{
tstring ini = ::INI_get_module_ini() ;
tstring str = ::To_tstring(val) ;
return ::INI_set(ini.c_str(),sec,ent,str.c_str()) ;
}
get の場合,文字列から変数に変換する方法をどうするか?
例えば,atoi や atof ,他にも 4 つの整数の文字列を RECT に変換するなど.
検索すると template で可能みたい だが…
よくわからなかったので,簡単な方法にした.
inline RECT To_RECT (LPCTSTR str)
{
RECT rect = { 0 } ;
{
v_tstring str_ary = ::String_SplitSpace(str,_T(" ,\t\r\n")) ;
if (0 < str_ary.size()) { rect.left = ::ttoi4(str_ary[0]) ; }
if (1 < str_ary.size()) { rect.top = ::ttoi4(str_ary[1]) ; }
if (2 < str_ary.size()) { rect.right = ::ttoi4(str_ary[2]) ; }
if (3 < str_ary.size()) { rect.bottom = ::ttoi4(str_ary[3]) ; }
}
return rect ;
}
inline bool string_to (LPCTSTR str,RECT* rect_) { *rect_ = ::To_RECT (str) ; return true ; }
inline bool string_to (LPCTSTR str,POINT* point) { *point = ::To_POINT(str) ; return true ; }
inline bool string_to (LPCTSTR str,SIZE* size_) { *size_ = ::To_SIZE (str) ; return true ; }
それぞれの型に合わせた関数を呼べるようになったので template に.
template <class T> T INI_get (LPCTSTR sec,LPCTSTR ent,const T& def)
{
tstring ini = ::INI_get_module_ini() ;
tstring dst = ::To_tstring(def) ;
tstring str = ::INI_get(ini.c_str(),sec,ent,dst.c_str()) ;
T val ;
::string_to(str.c_str(),&val) ;
return val ;
}
これで,次の様な使い方ができる様になる.
{
POINT point = ::POINT_set( 10, 20) ;
SIZE size_ = :: SIZE_set( 1111, 525) ;
RECT rect_ = :: RECT_set(point,size_) ;
{
::INI_set(_T("test"),_T("rect_"),rect_) ;
::INI_set(_T("test"),_T("point"),point) ;
::INI_set(_T("test"),_T("size_"),size_) ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("rect_"),rect_)) << std::endl ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("point"),point)) << std::endl ;
std::tout << ::To_tstring(::INI_get(_T("test"),_T("size_"),size_)) << std::endl ;
}
}
C++ NonCopyable
MFC を使用しないコードに書き直していて,代入できない構造体が欲しくなった.
オリジナルのコードは 20 年以上前のもので,CRegKey が簡単には使えなかった?頃.
「C++ クラス 代入できなくする」で検索.
コピー禁止を徹底させるNoncopyableクラス
More C++ Idioms/コピー禁止ミックスイン(Non-copyable Mixin)
明示的に既定された関数および削除された関数
MFC の CObject も同様と思いソースを見ると,やはり private になっている.
C++11 以降では =delete も使える.
関数のdefault/delete宣言
wiki C++11
これらを調べていて,次の所を見つけた.
旧時代のC言語を使うのはそろそろやめよう。
2016年、C言語はどう書くべきか (前編)
2016年、C言語はどう書くべきか (後編)
幾つかは既に意識しているが,Windows に依存する部分はなかなかできてない.
RegOpenKeyEx REGSAM
レジストリアクセスのコードを書き直していて,::RegOpenKeyEx の samDesired を調べてみた.
KEY_READ (0x20019) 0010 0000 0000 0001 1001
KEY_WRITE (0x20006) 0010 0000 0000 0000 0110
KEY_EXECUTE (0x20019) 0010 0000 0000 0001 1001
KEY_QUERY_VALUE (0x0001) 0000 0000 0000 0001
KEY_SET_VALUE (0x0002) 0000 0000 0000 0010
KEY_CREATE_SUB_KEY (0x0004) 0000 0000 0000 0100
KEY_ENUMERATE_SUB_KEYS (0x0008) 0000 0000 0000 1000
KEY_NOTIFY (0x0010) 0000 0000 0001 0000
KEY_CREATE_LINK (0x0020) 0000 0000 0010 0000
KEY_WOW64_64KEY (0x0100) 0000 0001 0000 0000
KEY_WOW64_32KEY (0x0200) 0000 0010 0000 0000
KEY_ALL_ACCESS (0xF003F) 1111 0000 0000 0011 1111
読み込み時,KEY_READ の方が速いなどはあるのか?それともファイルアクセスなどと同じ?
GetMonitorInfo
2002/08 に,マルチディスプレイ対応のコードを書いている.
今回,高 DPI 対応やディスプレイ位置が変わった時などのためもう一度…
ChatGPT で.
そのままでは VC 6 ではうまくビルドできなかったので,VC 8 で.
printf を使用しているので #include <cstdio> が必要.
そのまま実行すると 1920×1080 となる.
高 DPI スケール設定を「アプリケーション」とすると,3840×2160 .
2023/04/14
以前のコードを見ると,プライマリのみの情報は ::SystemParametersInfo などを使用している.
これで求められる SPI_GETWORKAREA は,::GetMonitorInfo で戻されるものと同じ?
システム 0 61 1920 1080
アプリケーション 0 122 3840 2160
::GetSystemMetrics(SM_?VIRTUALSCREEN) は,-1920 0 .
::GetSystemMetrics(SM_C?VIRTUALSCREEN) は,上をずらしているので少し異なる.
システム 3840 1210
アプリケーション 5760 2160
https://learn.microsoft.com/ja-jp/windows/win32/gdi/the-virtual-screen
MFC タイトルバーの変更
ダイアログベースであれば C…Dlg::OnInitDialog() に次の様なコードを追加.
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title+= _T(" ") + ::Get_ModuleVersion() + ::Get_BuildStrMSC() ;
SetWindowText(str_title.c_str()) ;
}
SDI や MDI の場合は,
MainFrm.h に OnUpdateFrameTitle を追加.
virtual void OnUpdateFrameTitle (BOOL bAddToTitle);
MainFrm.cpp に次の様な OnUpdateFrameTitle を追加.
// SDI
void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
CFrameWnd::OnUpdateFrameTitle(bAddToTitle) ;
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title+= _T(" ") + ::Get_ModuleVersion() + ::Get_BuildStrMSC() ;
SetWindowText(str_title.c_str()) ;
}
}
// MDI
void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
{
CMDIFrameWnd::OnUpdateFrameTitle(bAddToTitle) ;
{
tstring str_title = ::GetWindowText(this->GetSafeHwnd()) ;
str_title = ::MDI_Add_VerBuildStr(str_title.c_str()) ;
SetWindowText(str_title.c_str()) ;
}
}
高 DPI CToolBar のリサイズ
CToolBar を使用した 自前のコード .
CDialogBar や CStatusBar はスケーリングしてくれるのに,CToolBar は対応しない?
以前は設定などで変更可能なコードにしていたが,DPI を求める方法に.
if (newExtendSize == CSize(0,0)) {
double dpi_s = ::GetDPI_scale(toolBar->GetSafeHwnd()) ; // ::GetDpiForWindow()/96.
if (dpi_s > 1) {
CToolBarCtrl& tbCtrl = toolBar->GetToolBarCtrl() ;
CSize orgSize = tbCtrl.GetButtonSize() ;
CSize newSize = orgSize ;
newSize.cx = int(orgSize.cx*dpi_s) ;
newSize.cy = int(orgSize.cy*dpi_s) ;
newExtendSize.cx = newSize.cx - orgSize.cx ;
newExtendSize.cy = newSize.cy - orgSize.cy ;
resizeType = 'R' ;
}
}
あとは既存コードの,ビットマップのリサイズと CToolBar::SetSizes を呼出せば良さそう.
CMFCToolBar は DPI を正しく処理しているみたいで,その部分のコード.
…\VC\…\atlmfc\src\mfc\afxtoolbar.cpp の CMFCToolBar::LoadToolBarEx .
まだ幾つか問題はあるが,今回の対応部分としてはこれで良さそう.
更にツールバー上のコンボボックスのスケーリングは次の様にした.
{
int width = 50 ;
{
double dpi_s = 1. ;
dpi_s = ::GetDPI_scale(AfxGetMainWnd()->GetSafeHwnd()) ;
dpi_s = ::GetDPI_scale( m_wndToolBar.GetSafeHwnd()) ;
dpi_s = ::GetDPI_scale( this->GetSafeHwnd()) ;
if (dpi_s > 1.) {
width = int(width*dpi_s) ;
}
}
m_wndToolBar.SetButtonInfo (19,ID_COMBO_ANGLE,TBBS_SEPARATOR,width) ;
CRect rectCombo ;
m_wndToolBar.GetItemRect (19,&rectCombo) ;
rectCombo.top = 1 ;
rectCombo.bottom = rectCombo.top + 300 ;
if (!m_ComboAngle.Create(CBS_DROPDOWN | WS_VSCROLL | WS_VISIBLE,rectCombo,&m_wndToolBar,ID_COMBO_ANGLE)) {
TRACE0("Failed to ComboBox\n");
return -1;
}
m_ComboAngle.SetFont(m_wndToolBar.GetFont()) ;
m_ComboAngle.AddString(_T(" 0.")) ;
m_ComboAngle.AddString(_T("10.")) ;
// ...
m_ComboAngle.AddString(_T("90.")) ;
}
高 DPI テスト exe
先日の exe をテストしていると…
高 DPI をテストするために,次の様なコードの exe を作成.
void CT_aesDlg::OnDropFiles(HDROP hDropInfo)
{
v_tstring drop_files = ::DropFilesTo(hDropInfo) ;
for (size_t index=0 ; index<drop_files.size() ; index++) {
tstring drop_file = drop_files[index] ;
tstring ext = ::Path_GetExtLow(drop_file) ;
if (ext != _T("exe")) { continue ; }
{
S_Exec se ;
se.SetFile(drop_file.c_str()) ;
se.Execute() ;
}
}
CDialog::OnDropFiles(hDropInfo);
}
この exe に,他の exe をドロップして起動すると,「高 DPI スケール設定」が引き継がれる.
これらの設定は,次の所に持っている?
HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
A ~ HIGHDPIAWARE
S ~ DPIUNAWARE
E ~ GDIDPISCALING DPIUNAWARE
それぞれを実行すると表示が異なることはわかるが…
左から,「システム」,「システム(拡張)」,「アプリケーション」.
「システム」と「システム(拡張)」を区別する方法がわからない.
また「システム(拡張)」.exe で,何かの情報の取得が違っていて 表示が正しくない ものと思う.
::GetDeviceCaps の情報の表示は dc.DrawText(str,rect,DT_LEFT) としている.
この時,表示するフォントを指定していないため,「アプリケーション」では小さくなってしまう.
CFont::CreatePointFont などを呼ぶことで対応可能.
tcsncpy_s Buffer is too small
先日更新した ツール をテストしていると,フォントによりアプリケーションエラー?となってしまう.
デバッガで追いかけると,
—————————
Microsoft Visual C++ Runtime Library
—————————
Debug Assertion Failed!
Program: c:\Temp\i_Tools\TToPA\Debug.120\TToPA.exe
File: f:\dd\vctools\crt\crtw32\h\tcsncpy_s.inl
Line: 62
Expression: (L"Buffer is too small" && 0)
For information on how your program can cause an assertion
failure, see the Visual C++ documentation on asserts.
(Press Retry to debug the application)
—————————
中止(A) 再試行(R) 無視(I)
—————————
CRichEditCtrl を使用した CHARFORMATW の szFaceName の指定がうまくなかった.
::TcsNCpy(cf.szFaceName,LF_FACESIZE-1,faceName,LF_FACESIZE-1) ;
::TcsNCpy は,幾つかの環境でビルドできる様にしたもので,今回の場合は ::tcsncpy_s と同様もの.
faceName に与えたものが L"Bahnschrift Light SemiCondensed" で,コピー先のバッファが足りないため.
正しくは,
::TcsNCpy(cf.szFaceName,LF_FACESIZE-0,faceName,LF_FACESIZE-1) ;
これらの動作は,LOGFONT の時にもよく利用する.
既存のコードを検索してみると,LF_FACESIZE を正しく指定できていた.
::TcsCpy(lf.lfFaceName,LF_FACESIZE,tstring(faceName).substr(0,LF_FACESIZE-1).c_str()) ;
与えるデータは LF_FACESIZE-1 としている.
GetGlyphOutline ::PolyPolygon
求めたアウトラインをウィンドウに表示できる様な変換のコードを書いたので,その動作のテスト.
{
HWND hWnd = ::GetConsoleHwnd() ;
RECT rect = ::GetClientRect(hWnd) ;
POINT point= ::RECT_center(rect) ;
int ch = ::RECT_width(rect)/str.length()*10/7 ;
{
HDC hDC = ::GetDC(hWnd) ;
{
#define face_A _T("Arial")
#define face_U _T("Meiryo UI")
#define face_W _T("Wingdings")
{
::ClearWindow(hWnd) ;
int ry = -30 ;
tstring face_name = face_U ;
vv_PLF vv_plf= ::GetTTOutline(hDC,point,false,str.c_str(),face_name.c_str(),ch,0,0,ry,FW_DONTCARE) ;
v_Vd2A v_v2a = ::ToVd2A(vv_plf) ;
::PolyPolygon(hDC,v_v2a) ;
}
}
::ReleaseDC(hWnd,hDC) ;
}
}
::GetTTOutline の中では,文字の傾き,文字列の回転,Y の反転,表示位置の移動を行っている.
更に三角形に分割.
vv_PLF vv_plf= ::GetTTOutline(hDC,point,false,str.c_str(),face_name.c_str(),ch,0,0,ry,FW_DONTCARE) ;
// v_Vd2A v_v2a = ::ToVd2A(vv_plf) ;
v_Vd2A v_v2a = ::PLF_triangulation(vv_plf) ;
ダイアログバーのイベント処理
あまり使わないと,すぐ忘れてしまう…
1999/10 に作成したプロジェクトを変更していて,ダイアログバーにコントロールを追加.
ClassWizard で CMainFrame に対してイベント処理を追加すれば良い.
今回の SDI.exe は,ビューでの処理のみなので「再描画」のコマンドメッセージを Post している.
void CMainFrame::OnUpdateRx()
{
PostMessage(WM_COMMAND,ID_REDRAW,0) ;
}
ビューでは次の様にしてコントロールにアクセスしている.
CMainFrame* pMain = (CMainFrame*)AfxGetApp()->m_pMainWnd ;
CDialogBar* dlgBar= &pMain->m_wndMyDialogBar ;
CComboBox* font = (CComboBox*) dlgBar->GetDlgItem(IDC_FONT_NAME) ;
CEdit* text = (CEdit*) dlgBar->GetDlgItem(IDC_STRING) ;
CEdit* e_rx = (CEdit*) dlgBar->GetDlgItem(IDC_RX) ;
CEdit* e_ry = (CEdit*) dlgBar->GetDlgItem(IDC_RY) ;
ダイアログバーの追加手順は次の所.
https://dev.mish.work/wordpress/2021/12/27/vc-6-dialog-bar/
ShowWindow SW_HIDE SW_SHOW
ダイアログに「ピクチャーコントロール」を追加して,それの再描画.
以前よくやっていた方法.
void CFOutLineDlg::OnHeight()
{
if (m_CountSpin.m_hWnd == NULL) { return ; }
UpdateData(TRUE) ;
InvalidateRect(NULL) ;
}
これだと描画対象以外のコントロールも再描画するのでちらついてしまう.
一度 ShowWindow で SW_HIDE して SW_SHOW することで,対象がコントロールだけになる.
void CFOutLineDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
if (m_CountSpin.m_hWnd == NULL) { return ; }
::FitWindow(this,&m_Image,5,TRUE,FALSE) ;
m_Image.ShowWindow(SW_HIDE) ;
m_Image.ShowWindow(SW_SHOW) ;
}
error C2061 , C2091 , C2809 , C2556
1997/06 に作成したプロジェクトをビルドすると…
--------------------Configuration: FontFam - Win32 Release--------------------
FontFam.exe - 0 error(s), 0 warning(s)
--------------------Configuration: FontFam - Win32 Debug--------------------
Compiling...
FontFDlg.cpp
c:\program files (x86)\microsoft visual studio\vc98\include\new(35) : error C2061: syntax error : identifier 'THIS_FILE'
c:\program files (x86)\microsoft visual studio\vc98\include\new(35) : error C2091: function returns function
c:\program files (x86)\microsoft visual studio\vc98\include\new(35) : error C2809: 'operator new' has no formal parameters
c:\program files (x86)\microsoft visual studio\vc98\include\new(36) : error C2061: syntax error : identifier 'THIS_FILE'
c:\program files (x86)\microsoft visual studio\vc98\include\new(37) : error C2091: function returns function
c:\program files (x86)\microsoft visual studio\vc98\include\new(37) : error C2556: 'void *(__cdecl *__cdecl operator new(void))(unsigned int,const struct std::nothrow_t &)' : overloaded function differs only by return type from 'void *(__cdecl *__cd
ecl operator new(void))(unsigned int)'
c:\program files (x86)\microsoft visual studio\vc98\include\new(35) : see declaration of 'new'
c:\program files (x86)\microsoft visual studio\vc98\include\new(41) : error C2061: syntax error : identifier 'THIS_FILE'
c:\program files (x86)\microsoft visual studio\vc98\include\new(42) : error C2091: function returns function
c:\program files (x86)\microsoft visual studio\vc98\include\new(42) : error C2556: 'void *(__cdecl *__cdecl operator new(void))(unsigned int,void *)' : overloaded function differs only by return type from 'void *(__cdecl *__cdecl operator new(void))
(unsigned int)'
c:\program files (x86)\microsoft visual studio\vc98\include\new(35) : see declaration of 'new'
c:\program files (x86)\microsoft visual studio\vc98\include\new(42) : error C2809: 'operator new' has no formal parameters
c:\program files (x86)\microsoft visual studio\vc98\include\new(42) : error C2065: '_P' : undeclared identifier
Error executing cl.exe.
FontFam.exe - 11 error(s), 0 warning(s)
以前は <memory> の インクルードを追加 することで対応していた.
原因は,幾つかのインクルードが次のコードより後に存在しているため?
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
GetGlyphOutline ポリゴン化
まだまだだが…
文字ごとのポリラインを取得できる様になったので,穴の処理と組み合わせてポリゴン化.
文字によっては,穴の処理でクロスしてしまうことがある.
https://itl.mish.work/Iwao/Doc/algo/div_cnvx/div_23_4.html
https://itl.mish.work/Iwao/Doc/algo/div_cnvx/div_23_5.html
GetGlyphOutline ポリラインの配列に
「MS ゴシック」と「MS Pゴシック」などの幅が,思っていたものと違った.
v_PLF TTPOLYGON_to (LPTTPOLYGONHEADER buf,const DWORD rSize)
{
vv_LQC vv_lqc = ::to_LQC(buf,rSize) ; // buf を座標の配列に
v_PLF v_plf = ::LQC_to(vv_lqc) ; // ポリラインに変換
return v_plf ;
}
v_PLF GetTTOutline (HDC hDC,const UINT chr,GLYPHMETRICS* gm,const UINT format=GGO_NATIVE)
{
v_PLF v_plf ;
{
MAT2 mat2 = { {0, 1}, {0, 0}, {0, 0}, {0, 1} } ;
DWORD dwSize = ::GetGlyphOutline(hDC,chr, format, gm, 0, NULL, &mat2 ) ;
if (dwSize != GDI_ERROR) {
v_char buffer(dwSize) ;
GLYPHMETRICS gm2 = { 0 } ;
::GetGlyphOutline(hDC, chr, format, &gm2, buffer.size(), &buffer[0], &mat2) ;
v_plf = TTPOLYGON_to((LPTTPOLYGONHEADER)&buffer[0],dwSize) ;
}
}
return v_plf ;
}
vv_PLF GetTTOutline (HDC hDC,LPCTSTR str,const bool tate,const UINT format=GGO_NATIVE)
{
vv_PLF vv_plf ;
double lastX = 0 ; // left
double lastY = 0 ; // top tate
int ch = 0 ;
int cw = 0 ;
{ // chracter size
TEXTMETRIC tm = { 0 } ;
::GetTextMetrics(hDC,&tm) ;
ch = tm.tmHeight ;
cw = tm.tmAveCharWidth ;
}
#ifdef _UNICODE
LPCTSTR strP = str ;
#else
unsigned char* strP = (unsigned char*)str ;
#endif
for (size_t mbCount=0 ; mbCount<_tcsclen(str) && *strP!='\0' ; mbCount++) {
UINT chr = 0 ;
#ifdef _UNICODE
{
chr = UINT(*strP) ;
strP++ ;
}
#else
{
chr = _mbsnextc(strP) ;
strP= _mbsinc(strP) ;
}
#endif
if (chr == _T('\r')) { lastX = 0 ; continue ; }
if (chr == _T('\n')) { lastY-= ch ; continue ; }
if (chr == _T('\t')) { lastX+= cw ; continue ; }
{
GLYPHMETRICS gm = { 0 } ;
v_PLF v_plf = ::GetTTOutline(hDC,chr,&gm,format) ;
if (tate) { lastY -= (double(gm.gmBlackBoxY)+gm.gmCellIncX)/2 ; }
else { }
Vd2 last(lastX,lastY) ;
for (size_t index=0 ; index<v_plf.size() ; index++) {
PLF plf = v_plf[index] ;
Vd2A v2a = ::V2_Translate(::ToVd2A(::ToVd3A(plf)),last) ;
v_plf[index] = PLF(PLF::line,::ToVd4A(::ToVd3A(v2a))) ;
}
vv_plf.push_back(v_plf) ;
if (tate) { }
else { lastX += gm.gmCellIncX ; }
}
}
return vv_plf ;
}
Q243285 の動作の確認
先日からやっている GetGlyphOutline .その Q243285 の動作を確認方法.
Q243285: HOWTO: Draw TrueType Glyph Outlines
その中の ::DrawT2Outline の動作を確認しようとコンソール AP でやってみたが,うまく表示されない.
コードを見ればわかるが,::PolyBezier に与えられている pt の y がマイナスの範囲になっている.
以前書いたコードで emf に出力するものがある.また,それの HWND への再生も.
それで,HDC に対して直接出力するのではなく,一度 emf に出力して,それを表示することに.
#include <clocale>
#include <iostream>
#include <fstream>
#include <Windows.h>
#include "Q243285.hxx"
#include "con_wnd.hxx"
#include "E_MF.hxx"
LOGFONT GetLogFont(
LPCTSTR faceName
, const bool tate
, const int ch
, const int cw = 0
, const double rx = 0
, const double ry = 0
, const int weight = FW_DONTCARE
)
{
LOGFONT lf = { 0 } ;
::GetObject(::GetStockObject(SYSTEM_FONT),sizeof(lf),&lf) ;
if (tate) { lf.lfHeight = (cw!=0)?cw:ch ; lf.lfWidth = ch/2 ; }
else { lf.lfHeight = ch ; lf.lfWidth = cw ; }
if (tate) { lf.lfEscapement = int((rx+ry-90)*10) ; lf.lfOrientation= int((ry-90)*10) ; }
else { lf.lfEscapement = int(rx*10) ; lf.lfOrientation= int(ry*10) ; }
lf.lfWeight = weight ;
::TcsCpy(lf.lfFaceName,LF_FACESIZE,tstring(faceName).substr(0,LF_FACESIZE-1).c_str()) ;
return lf ;
}
tstring Get_TMP_path (void)
{
tstring tmp_path ;
size_t size = MAX_PATH ;
tmp_path.resize(size,0) ;
::GetTempPath(DWORD(size),&tmp_path[0]) ;
tmp_path = tstring(tmp_path.c_str()) ;
return tmp_path ;
}
bool DrawOutline(HDC hDC,int xStart,int yStart,LPCTSTR str)
{
#ifdef _UNICODE
LPCTSTR strP = str ;
#else
unsigned char* strP = (unsigned char*)str ;
#endif
// tstring tmp_path = ::Get_i_Tools_tmp_date() ;
tstring tmp_path = ::Get_TMP_path() ;
tstring emf_name ;
for (size_t mbCount=0 ; mbCount<_tcsclen(str) && *strP!='\0' ; mbCount++) {
UINT chr = 0 ;
#ifdef _UNICODE
{
chr = UINT(*strP) ;
strP++ ;
}
#else
{
chr = _mbsnextc(strP) ;
strP = _mbsinc(strP) ;
}
#endif
{
tstring tcs ;
{
if (chr < 0xff) {
tcs += TCHAR(chr) ;
}
else {
tstring buf ; buf.resize(255,0) ;
::_itot(chr,&buf[0],16) ;
tcs = buf.c_str() ;
}
std::tout << tcs << std::endl ;
}
{
emf_name = tmp_path + tcs + _T(".emf") ;
}
}
{
E_MetaF em(emf_name.c_str()) ;
HDC eDC = em.Get_HDC() ;
{
MAT2 mat2 = { {0, 1}, {0, 0}, {0, 0}, {0, 1} };
GLYPHMETRICS gm = { 0 };
DWORD dwSize = ::GetGlyphOutline(hDC,chr, GGO_NATIVE, &gm, 0, NULL, &mat2);
if (dwSize != GDI_ERROR) {
std::vector<char> buffer(dwSize);
GLYPHMETRICS gm2 = { 0 };
::GetGlyphOutline(hDC, chr, GGO_NATIVE, &gm2, buffer.size(), &buffer[0], &mat2);
::DrawT2Outline (eDC,(LPTTPOLYGONHEADER)&buffer[0],dwSize) ;
}
}
}
{
E_MetaF::Play(::GetConsoleHwnd(),emf_name.c_str()) ;
}
}
::TextOut(hDC,xStart,yStart+150,str,_tcslen(str)) ;
return true ;
}
bool test (c_tstring& str)
{
std::terr << str << std::endl ;
{
HWND hWnd = ::GetConsoleHwnd () ;
{
HDC hDC = ::GetDC(hWnd) ;
{
#define face_A _T("Arial")
#define face_M _T("Meiryo UI")
#define face_W _T("Wingdings")
LOGFONT lf = ::GetLogFont(face_M,false,100) ;
HFONT hFont = ::CreateFontIndirect(&lf) ;
HFONT oFont = (HFONT)::SelectObject(hDC,hFont) ;
::DrawOutline(hDC,200,200,str.c_str()) ;
::SelectObject(hDC,oFont) ;
::DeleteObject(hFont) ;
}
::ReleaseDC(hWnd,hDC) ;
}
}
return true ;
}
GetGlyphOutline TTPOLYCURVE
TTPOLYCURVE の TT_PRIM_LINE と TT_PRIM_QSPLINE は昨日までのコードでできそう.
TT_PRIM_CSPLINE についてもコードはわかりそうだが,実際のデータがちょっとわからない.
Q243285 の ::DrawT2Outline では,処理はしていないみたい.
::MakeBezierFromQBSpline では Quadratic から Cubic にしている.
12: 2次ベジェ曲線を3次に変換する方法
2次ベジェ曲線から3次ベジェ曲線への変換
今まで使っていたコードの TT_PRIM_CSPLINE 部分も同様で,特に問題はなかったようにも思う.
曲線の種類と Vd2A(2D の double の配列)を持つ「LQC クラス」を用意するか.
その配列 v_LQC が 1つの TTPOLYGONHEADER となる?
‘1’,’2′,’3′ などは 1つの TTPOLYGONHEADER .’A’ や ‘b’ は 2つ,’B’ や ‘8’ は 3つとなる.
1つの文字を表現するために v_LQC の配列の vv_LQC を用意.
ここまでで ::GetGlyphOutline の出力を vv_LQC として変換できる.
この vv_LQC をポリラインに変換して,v_PLF(複数のポリラインなどを保持可能)に登録する.
これで文字列を vv_PLF(そのままでは輪郭,先日の穴の処理をしてポリゴンに)として保持できる.
2023/03/17
TT_PRIM_CSPLINE に関して
GetGlyphOutline で uFormat に GGO_NATIVE ではなく GGO_BEZIER を指定.
すると,制御点が 2 つのデータが返される.
ポリラインに変換するコードは次の様なもの?
Vd2A LQC_3_to (const LQC& lqc)
{
Vd2A pts ;
if (lqc.size() > 0) {
Vd2 q0(lqc[0]) ;
Vd2 q1(lqc[0]) ;
Vd2 q2(lqc[0]) ;
Vd2 q3(lqc[0]) ;
for (size_t index=0 ; index+3<lqc.size() ; index++) {
if (index == 0) { q0 = lqc[index+0] ; }
q1 = lqc[index+1] ;
q2 = lqc[index+2] ;
q3 = lqc[index+3] ;
Vd2A va = ::bezier(q0,q1,q2,q3) ;
pts.insert(pts.end(),va.begin(),va.end()) ;
q0 = q3 ;
}
}
return pts ;
}
GetGlyphOutline の続き
TTPOLYCURVE は,ポリラインはそのまま扱えるが,ベジェ曲線の場合はそれなりの処理が必要.
TT_PRIM_LINE , TT_PRIM_QSPLINE , TT_PRIM_CSPLINE
それで ChatGPT に Bezier と問合わせると,
2次ベジェ曲線
B(t) = (1-t)^2 * P0 + 2t(1-t) * P1 + t^2 * P2
3次ベジェ曲線
B(t) = (1-t)^3 * P0 + 3t(1-t)^2 * P1 + 3t^2(1-t) * P2 + t^3 * P3
w0 から w3 を求める部分の挿入位置が違っていたが,ほぼそのままのコード.
C++ でのコードは次の様なもの.5 年位前に作成した 3 次ベジェ曲線 に合わせたコード.
Vd3A bezier (const Vd3& q0,const Vd3& q1,const Vd3& q2,const long div_c)
{
Vd3A vd3a ;
for (long index=0 ; index<=div_c ; index++) {
double t = 1./div_c*index ;
double b0 = (1.-t)*(1.-t) ; // (1-t)^2
double b1 = 2.*t *(1.-t) ; // 2 * (1-t) * t
double b2 = t*t ; // t^2
double px = (b0*q0.x) + (b1*q1.x) + (b2*q2.x) ;
double py = (b0*q0.y) + (b1*q1.y) + (b2*q2.y) ;
double pz = (b0*q0.z) + (b1*q1.z) + (b2*q2.z) ;
vd3a.push_back(Vd3(px,py,pz)) ;
}
return vd3a ;
}
2023/03/14
最初,1 つの TT_PRIM_QSPLINE が次の様になっている場合の始点,終点と制御点の与え方がわからなかった.
(8,0) (5,0) (1,4) (1,8) … ‘O’ の左下,見やすい様に整数にしている.
::bezier を 2 回呼出す.それぞれの値は次の様に指定する.
(8,0) (5,0) (3,2)
(3,2) (1,4) (1,8)
(3,2) は (5,0) と (1,4) の中点.
まだうまくない部分はあるが,何とか.
GetGlyphOutline の再調査
今動作しているコードを見ると,20 年以上前に書き始めたもの.
UNICODE 対応などの変更はあるが,基本的な動作はほとんど変わっていない.
MFC や古い自前のコードに依存している部分があるので,書き直そうかと…
その当時,どこを参考にしたのかわからない.一番古い部分は 1998 になっている.
テスト用に作成したプロジェクトのスケルトンの作成も 1997/04 のものがある.
次の所を参考にしたものと思うが,内容がだいぶ変わっている.
Q243285: HOWTO: Draw TrueType Glyph Outlines
::GetGlyphOutline の呼び出し方はそれほど難しくないが,返された TTPOLYGONHEADER などが難解.
当時はあまり理解できず,単純にポリラインに変換(QSpline2Polyline)していた.
今回検索すると,TTPOLYGONHEADER と TTPOLYCURVE などが詳しく書かれている所があった.
GetGlyphOutlineからアウトラインデータをもらおう
文字アウトラインの取得
Q87115: HOWTO: GetGlyphOutline() Native Buffer Format
2023/03/13
ここ のコードの結果を,2D データとしてダンプ.
{
tstring tmp_path ;
{
size_t size = MAX_PATH ;
tmp_path.resize(size,0) ;
::GetTempPath(DWORD(size),&tmp_path[0]) ;
tmp_path = tstring(tmp_path.c_str()) ;
}
tstring out_ipl = tmp_path ;
{
// UINT chr = character ;
if (chr < 0xff) {
out_ipl += TCHAR(chr) ;
}
else {
tstring buf ; buf.resize(255,0) ;
::_itot(chr,&buf[0],16) ;
out_ipl += buf.c_str() ;
}
out_ipl += _T(".ipl") ;
}
std::tout << out_ipl << std::endl ;
std::ofstream ipl_file(out_ipl.c_str()) ;
for (size_t r_index=0 ; r_index<result.size() ; r_index++) {
std::vector <LINE> vd = result[r_index] ;
for (size_t v_index=0 ; v_index<vd.size() ; v_index++) {
LINE d = vd[v_index] ;
ipl_file << _T("L") << std::endl ;
for (size_t p_index=0 ; p_index<d.points.size() ; p_index++) {
Vec2 p = d.points[p_index] ;
ipl_file << p.x << _T(",") << p.y << std::endl ;
}
ipl_file << std::endl ;
}
}
ipl_file.close() ;
}
%TEMP%\A.ipl は次の様になる.
L
36.5313,22
26.3125,50.2344
16.125,22
L
44.4219,0
38.6875,16
13.9375,16
8.20313,0
1,0
22.0469,57
30.9844,57
52,0
ipl の形式 f_ipl.hxx
個人的に作成した次のツールで表示可能.
https://jml.mish.work/index.php/i-tools/i3dx.html
コンソール AP で DC の使用
ChatGPT で「Windows コンソールアプリで DC を使用する方法」と入力してみた.
すると,使えそうなコードが表示された.
#include <Windows.h>
#include "i_define.hxx"
#include "con_wnd.hxx"
int main() {
// 1. コンソールウィンドウのハンドルを取得する
HWND consoleWindow = ::GetConsoleWindow();
// 2. コンソールウィンドウのDCを取得する
HDC consoleDC = GetDC(consoleWindow);
// 3. DCを使用して描画する
TextOut(consoleDC, 10, 100, _T("Hello, World!"), _tcslen(_T("Hello, World!")));
// 4. DCを解放する
ReleaseDC(consoleWindow, consoleDC);
return 0;
}
そのままではエラーになる部分があったので,少し修正している.
コンソール AP のウィンドウで GDI が使えるのは知らなかった.
BMP を表示するコード.
{
// ...
tstring dib_name = file_name ;
if (::File_IsNothing(dib_name)) { return false ; }
i_DIB dib = ::DIB_Load(dib_name.c_str()) ;
{
HWND hWnd = ::GetConsoleWindow() ;
{
RECT rect = { 0 } ;
::GetWindowRect(hWnd,&rect) ;
HDC hDC = ::GetDC(hWnd) ;
::DrawDIB(hDC,rect,dib) ;
::ReleaseDC(hWnd,hDC) ;
}
}
// ...
}
{
HWND hWnd = ::GetConsoleHwnd () ;
D_I_E die ;
die.SetDocPath(dib_name.c_str()) ;
die.Draw(hWnd) ;
}