/** * @file * @brief GetGlyphOutlineの動作を検証するためのプログラムです。 * @author AKIYAMA Kouhei * @since 2007-02-14 */ #include #include #include #include #include #include class TestException : public std::exception { std::string msg_; public: TestException(const std::string &msg) : msg_(msg){} virtual const char * what() const throw() { return msg_.c_str();} }; /** * フォント選択済みデバイスコンテキストを表すクラスです。 * * 次の処理を自動的に行います。 * * - デバイスコンテキストの作成 * - フォントの作成 * - フォントの選択 * - テキストメトリクスの取得 * - フォントの解除 * - フォントの削除 * - デバイスコンテキストの削除 */ class DeviceContext { HDC hdc_; HFONT hNewFont_; HFONT hOldFont_; OUTLINETEXTMETRIC otm_; public: DeviceContext(int h = -14, int w = 0, int orientation = 0, int weight = 400) { hdc_ = ::CreateCompatibleDC(NULL); if(!hdc_){ throw TestException("CreateCompatibleDC failed."); } ::SetMapMode(hdc_, MM_TEXT); hNewFont_ = ::CreateFont(h, w, orientation, orientation, weight, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_TT_ONLY_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH|FF_DONTCARE, "MS ゴシック"); if(!hNewFont_){ ::DeleteDC(hdc_); throw TestException("CreateFont failed."); } hOldFont_ = (HFONT)::SelectObject(hdc_, hNewFont_); if(!hOldFont_ || hOldFont_ == HGDI_ERROR){ ::DeleteObject(hNewFont_); ::DeleteDC(hdc_); throw TestException("SelectObject failed."); } ::ZeroMemory(&otm_, sizeof(otm_)); if(!::GetOutlineTextMetrics(hdc_, sizeof(otm_), &otm_)){ ::SelectObject(hdc_, hOldFont_); ::DeleteObject(hNewFont_); ::DeleteDC(hdc_); throw TestException("GetOutlineTextMetrics failed."); } } ~DeviceContext() { ::SelectObject(hdc_, hOldFont_); ::DeleteObject(hNewFont_); ::DeleteDC(hdc_); } operator HDC() const { return hdc_; } int getAscent(void) const { return otm_.otmTextMetrics.tmAscent;} int getDescent(void) const { return otm_.otmTextMetrics.tmDescent;} int getHeight(void) const { return otm_.otmTextMetrics.tmHeight;} }; // ------------------------------------------------------------------ namespace TestWin98StaticConstMat { extern const MAT2 mat = {{0,1}, {0,0}, {0,0}, {0,1}}; } /** * Win98でMAT2をCONST SEGMENTに置くとGDI_ERRORになる問題を検証する関数です。 * * Win98でGDI_ERRORになります。ただし、Release版のときはインクリメンタルリンクを有効にしてmatのアドレスを調整する必要があるようです。 * Win2kではOKになります。 */ void testWin98StaticConstMat(void) { std::cout << "[testWin98StaticConstMat]" << std::endl; DeviceContext dc; // static const MAT2 mat = {{0,1}, {0,0}, {0,0}, {0,1}}; UINT code = 0x8140; GLYPHMETRICS gm; DWORD size = ::GetGlyphOutline(dc, code, GGO_GRAY8_BITMAP, &gm, 0, NULL, &TestWin98StaticConstMat::mat); if(size == GDI_ERROR){ std::cout << "GDI_ERROR" << std::endl; } else{ std::cout << "OK" << std::endl; } } // ------------------------------------------------------------------ /** * GetGlyphOutlineが0を返した時にGLYPHMETRICSのgmBlackBoxXとgmBlackBoxYが0にならない問題を検証する関数です。 * * Win98では0x20と0x8140だけ0を返し、gmBlackBoxXとgmBlackBoxYは共に0になります。 * Win2kでは0x20と0x8140以外でも0を返すものが多数有り、全ての場合においてgmBlackBoxXとgmBlackBoxYは共に1になります。 */ void testZeroSizeGlyph(void) { std::cout << "[testZeroSizeGlyph]" << std::endl; DeviceContext dc; for(UINT code = 0x20; code <= 0xffff; code++){ GLYPHMETRICS gm; /*static*/ const MAT2 mat = {{0,1}, {0,0}, {0,0}, {0,1}}; DWORD size = ::GetGlyphOutline(dc, code, GGO_GRAY8_BITMAP, &gm, 0, NULL, &mat); if(size == 0){ if(gm.gmBlackBoxX == 0 && gm.gmBlackBoxY == 0){ std::cout << "code:" << code << " size:0 but size just matched." << std::endl; } else{ std::cout << "code:" << code << " size:" << size << " bbx:" << gm.gmBlackBoxX << " bby:" << gm.gmBlackBoxY << std::endl; } } } } // ------------------------------------------------------------------ /** * GetGlyphOutlineが返すバイト数とGLYPHMETRICSのgmBlackBoxXとgmBlackBoxYから求めたバイト数とが一致しない問題を検証する関数です。 * Win2kで高さ18pxのMS ゴシックで「ぱ」の下が切れるといった問題につながります。 * 文字のサイズや回転角によって様々なケースがあるようです。 * * Win98で一致しないケースは今のところ見つかっていません。 */ void testGlyphSizeMismatch(void) { std::cout << "[testGlyphSizeMismatch]" << std::endl; for(int height = 17; height <= 17; height++){ for(int angle = 0; angle < 3600; angle += 900){ DeviceContext dc(-height, 0, angle); for(UINT code = 0x20; code <= 0xffff; code = (code == 0x100 ? 0x8140 : code + 1)){ GLYPHMETRICS gm; /*static*/ const MAT2 mat = {{0,1}, {0,0}, {0,0}, {0,1}}; DWORD size = ::GetGlyphOutline(dc, code, GGO_GRAY8_BITMAP, &gm, 0, NULL, &mat); if(size == GDI_ERROR){ std::cout << "h:" << height << " angle:" << angle << " code:" << code << " GDI_ERROR" << std::endl; } else{ //GDIエラーじゃない。 const int alignedWidth = (gm.gmBlackBoxX + 3) & ~3; const int idealSize = alignedWidth * gm.gmBlackBoxY; if(size != idealSize){ //サイズが理想と違う。 std::cout << "h:" << height << " angle:" << angle << " code: " << std::hex << code << std::dec << " size:" << size << " bb(" << gm.gmBlackBoxX << "," << gm.gmBlackBoxY << ")" << " o:(" << gm.gmptGlyphOrigin.x << "," << gm.gmptGlyphOrigin.y << ")"; if(size == 0){ std::cout << " size==0" << std::endl; } else if(idealSize == 0){ std::cout << " idealSize==0" << std::endl; } else if((int)size < idealSize){ std::cout << " danger!! (size < idealSize)" << std::endl; } else if(size == idealSize + alignedWidth){ //きっちり1ライン分多い。 std::cout << " +1line"; //上にはみ出た可能性をチェックする。 bool top = (angle == 0 && gm.gmptGlyphOrigin.y == dc.getAscent()) // || (angle == 900 && gm.gmptGlyphOrigin.y || (angle == 1800 && gm.gmptGlyphOrigin.y == dc.getDescent()) || (angle == 2700 && gm.gmptGlyphOrigin.y == 1) //文字の左側が上。 ; bool bottom = (angle == 0 && gm.gmptGlyphOrigin.y - gm.gmBlackBoxY == -dc.getDescent()) || (angle == 900 && gm.gmptGlyphOrigin.y == gm.gmBlackBoxY) || (angle == 1800 && gm.gmptGlyphOrigin.y - gm.gmBlackBoxY == -dc.getAscent()) || (angle == 2700 && gm.gmptGlyphOrigin.y - (gm.gmBlackBoxY + 1) == -std::abs(gm.gmCellIncY)) || (angle == 2700 && gm.gmptGlyphOrigin.y - (gm.gmBlackBoxY + 0) == -std::abs(gm.gmCellIncY)) ; if(top && bottom){ std::cout << " over top or bottom." << std::endl; } else if(top){ std::cout << " over top." << std::endl; } else if(bottom){ std::cout << " over bottom." << std::endl; } else{ std::cout << " unknown." << std::endl; } } else if(size == idealSize + alignedWidth*2){ //きっちり2ライン分多い。 std::cout << " +2lines"; std::cout << std::endl; } else{ std::cout << " unknown reason." << std::endl; } } } } } } } // ------------------------------------------------------------------ /** * CreateFontのnOrientationで回転させた場合、gmCellIncYがWin98とWin2kとで異なる問題を検証する関数です。 * * Win98では0:(14,0), 90:(0,14), 180:(-14,0), 270:(0,-14)となります。 * Win2kでは0:(14,0), 90:(0,-14), 180:(-14,0), 270:(0,14)となります。 */ void testCellIncInRotating(void) { std::cout << "[testCellIncInRotating]" << std::endl; for(int angle = 0; angle < 3600; angle += 900){ DeviceContext dc(-14, 0, angle); UINT code = 0x82a0; GLYPHMETRICS gm; /*static*/ const MAT2 mat = {{0,1}, {0,0}, {0,0}, {0,1}}; DWORD size = ::GetGlyphOutline(dc, code, GGO_GRAY8_BITMAP, &gm, 0, NULL, &mat); std::cout << "code:" << code << " angle:" << angle; if(size == GDI_ERROR){ std::cout << " GDI_ERROR" << std::endl; } else{ std::cout << " cellinc:(" << gm.gmCellIncX << "," << gm.gmCellIncY << ")"; if(gm.gmCellIncY < 0){ std::cout << " gmCellIncY is negative." << std::endl; } else if(gm.gmCellIncY > 0){ std::cout << " gmCellIncY is positive." << std::endl; } else{ std::cout << " gmCellIncY is zero." << std::endl; } } } } // ------------------------------------------------------------------ namespace TestStackDelta40 { const int DMY_BYTES = 6; int dmy[DMY_BYTES]; GLYPHMETRICS gm; MAT2 GGO_MAT = {{0,1}, {0,0}, {0,0}, {0,1}}; void f2(DeviceContext &dc, unsigned int code, unsigned char *buffer, unsigned int size) { int a = dmy[0]; int b = dmy[1]; int c = dmy[2]; int d = dmy[3]; // f2内にある「call GlyphOutline」の直前のespの値は、f1内にある「call GetGlyphOutline」の直前のespの値より28h小さいこと。 const DWORD result = ::GetGlyphOutline(dc, code, GGO_GRAY8_BITMAP, &gm, size, buffer, &GGO_MAT); if(size == GDI_ERROR){ std::cout << "GDI_ERROR in f2" << std::endl; } dmy[5] = a * b; dmy[4] = a * b * c; dmy[3] = a * b - c; dmy[2] = a + b * d; dmy[1] = a - b - d; } void f1(DeviceContext &dc, unsigned int code, std::vector &buffer) { // ここをGGO_GRAY4_BITMATにすると正しく動作するようになる。サイズはGGO_GRAY8_BITMAPのときと変わらず(保証無し)。 DWORD size = ::GetGlyphOutline(dc, code, GGO_GRAY8_BITMAP, &gm, 0, NULL, &GGO_MAT); if(size == GDI_ERROR){ std::cout << "GDI_ERROR in f1" << std::endl; return; } buffer.resize(size); f2(dc, code, &buffer[0], size); } void f3(DeviceContext &dc, unsigned int code, std::vector &buffer) { DWORD size = ::GetGlyphOutline(dc, code, GGO_GRAY8_BITMAP, &gm, 0, NULL, &GGO_MAT); if(size == GDI_ERROR){ std::cout << "GDI_ERROR in f3(1)" << std::endl; return; } buffer.resize(size); const DWORD result = ::GetGlyphOutline(dc, code, GGO_GRAY8_BITMAP, &gm, size, &buffer[0], &GGO_MAT); if(size == GDI_ERROR){ std::cout << "GDI_ERROR in f3(2)" << std::endl; return; } } ///インラインアセンブラで直接的にespをずらすバージョン。 void f1_asm(HDC hdc, unsigned int code, std::vector &buffer) { static const int MAX_SIZE = 65536; buffer.resize(MAX_SIZE); unsigned char *bufferTop = &buffer[0]; DWORD size1 = GDI_ERROR; DWORD size2 = GDI_ERROR; static const int STACK_DELTA = 0x28; __asm{ push offset GGO_MAT push 0 push 0 push offset gm push GGO_GRAY8_BITMAP push code push hdc call dword ptr [GetGlyphOutline] mov size1, eax cmp eax, -1 je error cmp eax, MAX_SIZE jg error sub esp, STACK_DELTA ;; スタックポインタをずらす。 push offset GGO_MAT push bufferTop push eax push offset gm push GGO_GRAY8_BITMAP push code push hdc call dword ptr [GetGlyphOutline] mov size2, eax add esp, STACK_DELTA ;; ずらしたスタックポインタを戻す。 error: } if(size1 == size2 && size1 != GDI_ERROR){ buffer.resize(size1); } } }//namespace TestStackDelta40 /** * GetGlyphOutlineを二回連続で呼び出す時、一回目呼び出し時のスタックポインタと二回目呼び出し時のスタックポインタとの差がちょうど28hのときに得られる字形が乱れる問題を検証する関数です。 * * Win98のときmismatchedになります。 * Win2kのときOKになります。 * * 所によっては54hの差で起こることも確認しています。よく分からない。 */ void testWin98StackDelta40(void) { std::cout << "[testWin98StackDelta40]" << std::endl; DeviceContext dc(14); std::vector buffer1; TestStackDelta40::f1_asm(dc, 0x9867, buffer1); std::vector buffer2; TestStackDelta40::f3(dc, 0x9867, buffer2); if(buffer1.size() != buffer2.size()){ std::cout << "size mismatched." << std::endl; return; } if(buffer1 == buffer2){ std::cout << "OK" << std::endl; } else{ std::cout << "mismatched." << std::endl; } } // ------------------------------------------------------------------ //int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) int main(int argc, char **argv) { testWin98StaticConstMat(); testZeroSizeGlyph(); testGlyphSizeMismatch(); testCellIncInRotating(); testWin98StackDelta40(); std::cout << "\n"; std::cout << "finished." << std::endl; return 0; }