diff --git a/demo/test_ege_setfont.cpp b/demo/test_ege_setfont.cpp new file mode 100644 index 0000000..b910fb7 --- /dev/null +++ b/demo/test_ege_setfont.cpp @@ -0,0 +1,79 @@ +/* + * Test demo for ege_setfont function + * This demo demonstrates the use of floating-point font sizes with GDI+ + * Font sizes are in pixels to match the existing setfont() behavior + */ + +#include +#include + +int main() +{ + // Initialize graphics window + initgraph(800, 600); + setbkcolor(WHITE); + cleardevice(); + + // Test 1: Basic ege_setfont with floating-point size + settextcolor(BLACK); + ege_setfont(24.5f, L"Arial"); + ege_drawtext(L"24.5px Arial font (floating-point size)", 50.0f, 50.0f); + + // Test 2: Different sizes to show precision + ege_setfont(12.0f, L"Times New Roman"); + ege_drawtext(L"12.0px Times New Roman", 50.0f, 100.0f); + + ege_setfont(12.5f, L"Times New Roman"); + ege_drawtext(L"12.5px Times New Roman", 50.0f, 120.0f); + + ege_setfont(13.0f, L"Times New Roman"); + ege_drawtext(L"13.0px Times New Roman", 50.0f, 140.0f); + + // Test 3: With font styles + ege_setfont(20.0f, L"Arial", Gdiplus::FontStyleBold); + ege_drawtext(L"20px Arial Bold", 50.0f, 200.0f); + + ege_setfont(18.0f, L"Arial", Gdiplus::FontStyleItalic); + ege_drawtext(L"18px Arial Italic", 50.0f, 240.0f); + + ege_setfont(16.0f, L"Arial", Gdiplus::FontStyleBold | Gdiplus::FontStyleItalic); + ege_drawtext(L"16px Arial Bold Italic", 50.0f, 280.0f); + + ege_setfont(14.0f, L"Arial", Gdiplus::FontStyleUnderline); + ege_drawtext(L"14px Arial Underline", 50.0f, 320.0f); + + // Test 4: Very precise sizes + ege_setfont(15.25f, L"Courier New"); + ege_drawtext(L"15.25px Courier New (very precise)", 50.0f, 380.0f); + + // Test 5: Chinese characters with floating-point size + ege_setfont(22.5f, L"SimSun"); + ege_drawtext(L"22.5像素宋体 - 中文测试", 50.0f, 430.0f); + + // Test 6: Compare with measuretext + float width, height; + const wchar_t* testText = L"Test measuretext with GDI+ Font"; + ege_setfont(18.0f, L"Arial"); + measuretext(testText, &width, &height); + + const size_t INFO_BUFFER_SIZE = 256; + wchar_t info[INFO_BUFFER_SIZE]; +#if defined(_MSC_VER) && (_MSC_VER >= 1400) + swprintf_s(info, INFO_BUFFER_SIZE, L"Width: %.2f, Height: %.2f", width, height); +#else + swprintf(info, INFO_BUFFER_SIZE, L"Width: %.2f, Height: %.2f", width, height); +#endif + ege_drawtext(testText, 50.0f, 480.0f); + ege_setfont(12.0f, L"Arial"); + ege_drawtext(info, 50.0f, 510.0f); + + // Instructions + ege_setfont(14.0f, L"Arial"); + settextcolor(BLUE); + ege_drawtext(L"Press any key to exit...", 50.0f, 550.0f); + + // Wait for key press + getch(); + closegraph(); + return 0; +} diff --git a/include/ege.h b/include/ege.h index 4e9aac3..a7f9109 100644 --- a/include/ege.h +++ b/include/ege.h @@ -3996,6 +3996,56 @@ void EGEAPI setfont(const LOGFONTA *font, PIMAGE pimg = NULL); EGE_DEPRECATE(getfont, "Please use the 'getfont' function with the LOGFONTW* parameter instead.") void EGEAPI getfont(LOGFONTA *font, PCIMAGE pimg = NULL); +/** + * @brief Set font for GDI+ text rendering with floating-point size + * @param size Font size (floating-point), measured in cell height pixels to match GDI setfont() behavior + * @param typeface Font family name + * @param pimg Target image pointer, NULL means current ege window + * @note This function creates a GDI+ Font directly, allowing floating-point font sizes. + * When set, ege_drawtext will use this GDI+ font instead of converting from GDI HFONT. + * The size represents the cell height (like GDI's LOGFONT.lfHeight). The implementation + * adjusts this to GDI+'s em height internally to ensure the rendered size matches GDI fonts. + */ +void EGEAPI ege_setfont(float size, const char* typeface, PIMAGE pimg = NULL); + +/** + * @brief Set font for GDI+ text rendering with floating-point size (Unicode) + * @param size Font size (floating-point), measured in cell height pixels to match GDI setfont() behavior + * @param typeface Font family name + * @param pimg Target image pointer, NULL means current ege window + * @note This function creates a GDI+ Font directly, allowing floating-point font sizes. + * When set, ege_drawtext will use this GDI+ font instead of converting from GDI HFONT. + * The size represents the cell height (like GDI's LOGFONT.lfHeight). The implementation + * adjusts this to GDI+'s em height internally to ensure the rendered size matches GDI fonts. + */ +void EGEAPI ege_setfont(float size, const wchar_t* typeface, PIMAGE pimg = NULL); + +/** + * @brief Set font for GDI+ text rendering with floating-point size and style + * @param size Font size (floating-point), measured in cell height pixels to match GDI setfont() behavior + * @param typeface Font family name + * @param style Font style (combination of Gdiplus::FontStyle flags: FontStyleBold, FontStyleItalic, etc.) + * @param pimg Target image pointer, NULL means current ege window + * @note This function creates a GDI+ Font directly, allowing floating-point font sizes and GDI+ font styles. + * When set, ege_drawtext will use this GDI+ font instead of converting from GDI HFONT. + * The size represents the cell height (like GDI's LOGFONT.lfHeight). The implementation + * adjusts this to GDI+'s em height internally to ensure the rendered size matches GDI fonts. + */ +void EGEAPI ege_setfont(float size, const char* typeface, int style, PIMAGE pimg = NULL); + +/** + * @brief Set font for GDI+ text rendering with floating-point size and style (Unicode) + * @param size Font size (floating-point), measured in cell height pixels to match GDI setfont() behavior + * @param typeface Font family name + * @param style Font style (combination of Gdiplus::FontStyle flags: FontStyleBold, FontStyleItalic, etc.) + * @param pimg Target image pointer, NULL means current ege window + * @note This function creates a GDI+ Font directly, allowing floating-point font sizes and GDI+ font styles. + * When set, ege_drawtext will use this GDI+ font instead of converting from GDI HFONT. + * The size represents the cell height (like GDI's LOGFONT.lfHeight). The implementation + * adjusts this to GDI+'s em height internally to ensure the rendered size matches GDI fonts. + */ +void EGEAPI ege_setfont(float size, const wchar_t* typeface, int style, PIMAGE pimg = NULL); + /// @} #define getmaxx getwidth diff --git a/include/ege.zh_CN.h b/include/ege.zh_CN.h index a22561a..71c2fa4 100644 --- a/include/ege.zh_CN.h +++ b/include/ege.zh_CN.h @@ -3991,6 +3991,56 @@ void EGEAPI setfont(const LOGFONTA *font, PIMAGE pimg = NULL); EGE_DEPRECATE(getfont, "Please use the 'getfont' function with the LOGFONTW* parameter instead.") void EGEAPI getfont(LOGFONTA *font, PCIMAGE pimg = NULL); +/** + * @brief 设置用于GDI+文字渲染的浮点数大小字体 + * @param size 字体大小(浮点数),以单元格高度像素为单位,与GDI setfont()行为保持一致 + * @param typeface 字体名称 + * @param pimg 目标图像指针,NULL 表示当前ege窗口 + * @note 此函数直接创建GDI+ Font,允许使用浮点数字体大小。 + * 设置后,ege_drawtext将使用此GDI+字体,而不是从GDI HFONT转换。 + * 大小表示单元格高度(类似GDI的LOGFONT.lfHeight)。实现内部会将其调整为 + * GDI+的em高度,以确保渲染大小与GDI字体一致。 + */ +void EGEAPI ege_setfont(float size, const char* typeface, PIMAGE pimg = NULL); + +/** + * @brief 设置用于GDI+文字渲染的浮点数大小字体(Unicode版本) + * @param size 字体大小(浮点数),以单元格高度像素为单位,与GDI setfont()行为保持一致 + * @param typeface 字体名称 + * @param pimg 目标图像指针,NULL 表示当前ege窗口 + * @note 此函数直接创建GDI+ Font,允许使用浮点数字体大小。 + * 设置后,ege_drawtext将使用此GDI+字体,而不是从GDI HFONT转换。 + * 大小表示单元格高度(类似GDI的LOGFONT.lfHeight)。实现内部会将其调整为 + * GDI+的em高度,以确保渲染大小与GDI字体一致。 + */ +void EGEAPI ege_setfont(float size, const wchar_t* typeface, PIMAGE pimg = NULL); + +/** + * @brief 设置用于GDI+文字渲染的浮点数大小和样式字体 + * @param size 字体大小(浮点数),以单元格高度像素为单位,与GDI setfont()行为保持一致 + * @param typeface 字体名称 + * @param style 字体样式(Gdiplus::FontStyle标志的组合:FontStyleBold、FontStyleItalic等) + * @param pimg 目标图像指针,NULL 表示当前ege窗口 + * @note 此函数直接创建GDI+ Font,允许使用浮点数字体大小和GDI+字体样式。 + * 设置后,ege_drawtext将使用此GDI+字体,而不是从GDI HFONT转换。 + * 大小表示单元格高度(类似GDI的LOGFONT.lfHeight)。实现内部会将其调整为 + * GDI+的em高度,以确保渲染大小与GDI字体一致。 + */ +void EGEAPI ege_setfont(float size, const char* typeface, int style, PIMAGE pimg = NULL); + +/** + * @brief 设置用于GDI+文字渲染的浮点数大小和样式字体(Unicode版本) + * @param size 字体大小(浮点数),以单元格高度像素为单位,与GDI setfont()行为保持一致 + * @param typeface 字体名称 + * @param style 字体样式(Gdiplus::FontStyle标志的组合:FontStyleBold、FontStyleItalic等) + * @param pimg 目标图像指针,NULL 表示当前ege窗口 + * @note 此函数直接创建GDI+ Font,允许使用浮点数字体大小和GDI+字体样式。 + * 设置后,ege_drawtext将使用此GDI+字体,而不是从GDI HFONT转换。 + * 大小表示单元格高度(类似GDI的LOGFONT.lfHeight)。实现内部会将其调整为 + * GDI+的em高度,以确保渲染大小与GDI字体一致。 + */ +void EGEAPI ege_setfont(float size, const wchar_t* typeface, int style, PIMAGE pimg = NULL); + /// @} #define getmaxx getwidth diff --git a/src/font.cpp b/src/font.cpp index c508705..8a29b2e 100644 --- a/src/font.cpp +++ b/src/font.cpp @@ -325,8 +325,16 @@ void measuretext(const wchar_t* text, float* width, float* height, PCIMAGE pimg) if (!isEmpty(text) && img && img->m_hDC) { using namespace Gdiplus; - HFONT hFont = (HFONT)GetCurrentObject(img->m_hDC, OBJ_FONT); - Font font(img->m_hDC, hFont); + // Use stored GDI+ font if available, otherwise create from GDI HFONT + Font* fontPtr = NULL; + bool useStoredFont = (img->m_font != NULL); + + if (useStoredFont) { + fontPtr = img->m_font; + } else { + HFONT hFont = (HFONT)GetCurrentObject(img->m_hDC, OBJ_FONT); + fontPtr = new Font(img->m_hDC, hFont); + } Graphics graphics(img->m_hDC); @@ -337,23 +345,29 @@ void measuretext(const wchar_t* text, float* width, float* height, PCIMAGE pimg) case RIGHT_TEXT: format->SetAlignment(StringAlignmentFar); break; default: break; } - format->SetFormatFlags(StringFormatFlagsMeasureTrailingSpaces); + + // Match the behavior in ege_drawtext_p: only measure trailing spaces for non-left alignment + if (img->m_texttype.horiz != LEFT_TEXT) { + format->SetFormatFlags(format->GetFormatFlags() | StringFormatFlagsMeasureTrailingSpaces); + } int textLength = (int)wcslen(text); - CharacterRange charRange(0, textLength); - format->SetMeasurableCharacterRanges(1, &charRange); - + + // Use MeasureString instead of MeasureCharacterRanges for better accuracy + // MeasureString gives bounds that match what DrawString actually renders + Gdiplus::RectF boundRect; Gdiplus::RectF layoutRect(0, 0, 65535, 65535); - Region region; - if (graphics.MeasureCharacterRanges(text, textLength, &font, layoutRect, format, 1, ®ion) == Ok) { - Gdiplus::RectF boundRect; - if (region.GetBounds(&boundRect, &graphics) == Ok) { - textWidth = boundRect.Width; - textHeight = boundRect.Height; - } + if (graphics.MeasureString(text, textLength, fontPtr, layoutRect, format, &boundRect) == Ok) { + textWidth = boundRect.Width; + textHeight = boundRect.Height; } delete format; + + // Clean up temporary font if created + if (!useStoredFont) { + delete fontPtr; + } } if (width != NULL) @@ -785,17 +799,24 @@ static void ege_drawtext_p(const wchar_t* textstring, float x, float y, PIMAGE i using namespace Gdiplus; Gdiplus::Graphics* graphics = img->getGraphics(); - HFONT hf = (HFONT)GetCurrentObject(img->m_hDC, OBJ_FONT); - LOGFONTW lf; - GetObjectW(hf, sizeof(LOGFONTW), &lf); - if (wcscmp(lf.lfFaceName, L"System") == 0) { - hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + // Use stored GDI+ font if available, otherwise create from GDI HFONT + Gdiplus::Font* fontPtr = NULL; + bool useStoredFont = (img->m_font != NULL); + + if (useStoredFont) { + fontPtr = img->m_font; + } else { + HFONT hf = (HFONT)GetCurrentObject(img->m_hDC, OBJ_FONT); + LOGFONTW lf; + GetObjectW(hf, sizeof(LOGFONTW), &lf); + if (wcscmp(lf.lfFaceName, L"System") == 0) { + hf = (HFONT)GetStockObject(DEFAULT_GUI_FONT); + } + fontPtr = new Gdiplus::Font(img->m_hDC, hf); } - Gdiplus::Font font(img->m_hDC, hf); - - // if (!font.IsAvailable()) { - // fprintf(stderr, "!font.IsAvailable(), hf: %p\n", hf); + // if (!fontPtr->IsAvailable()) { + // fprintf(stderr, "!fontPtr->IsAvailable()\n"); // } Gdiplus::PointF origin(x, y); Gdiplus::SolidBrush brush(img->m_textcolor); @@ -823,25 +844,32 @@ static void ege_drawtext_p(const wchar_t* textstring, float x, float y, PIMAGE i float xScale = 1.0f, angle = 0.0f; - if (lf.lfWidth != 0) { - LONG fixedWidth = lf.lfWidth; - float tmp; - float textCurrentWidth; - measuretext(textstring, &textCurrentWidth, &tmp, img); - lf.lfWidth = 0; - setfont(&lf, img); - float textNormalWidth; - measuretext(textstring, &textNormalWidth, &tmp, img); - lf.lfWidth = fixedWidth; - setfont(&lf, img); - - if ((int)textCurrentWidth != (int)textNormalWidth && ((int)textCurrentWidth != 0) && ((int)textNormalWidth != 0)) { - xScale = textCurrentWidth / textNormalWidth; + // Only apply width scaling and escapement for GDI fonts (not for stored GDI+ fonts) + if (!useStoredFont) { + LOGFONTW lf; + HFONT hf = (HFONT)GetCurrentObject(img->m_hDC, OBJ_FONT); + GetObjectW(hf, sizeof(LOGFONTW), &lf); + + if (lf.lfWidth != 0) { + LONG fixedWidth = lf.lfWidth; + float tmp; + float textCurrentWidth; + measuretext(textstring, &textCurrentWidth, &tmp, img); + lf.lfWidth = 0; + setfont(&lf, img); + float textNormalWidth; + measuretext(textstring, &textNormalWidth, &tmp, img); + lf.lfWidth = fixedWidth; + setfont(&lf, img); + + if ((int)textCurrentWidth != (int)textNormalWidth && ((int)textCurrentWidth != 0) && ((int)textNormalWidth != 0)) { + xScale = textCurrentWidth / textNormalWidth; + } } - } - if (lf.lfEscapement % 3600 != 0) { - angle = (float)(-lf.lfEscapement / 10.0); + if (lf.lfEscapement % 3600 != 0) { + angle = (float)(-lf.lfEscapement / 10.0); + } } if ((xScale != 1.0) || (angle != 0.0f)) { @@ -857,13 +885,125 @@ static void ege_drawtext_p(const wchar_t* textstring, float x, float y, PIMAGE i graphics->ScaleTransform(xScale, 1.0f); } - graphics->DrawString(textstring, -1, &font, Gdiplus::PointF(0, 0), format, &brush); + graphics->DrawString(textstring, -1, fontPtr, Gdiplus::PointF(0, 0), format, &brush); graphics->SetTransform(&oldTransformMatrix); } else { - graphics->DrawString(textstring, -1, &font, origin, format, &brush); + graphics->DrawString(textstring, -1, fontPtr, origin, format, &brush); } delete format; + + // Clean up temporary font if created + if (!useStoredFont) { + delete fontPtr; + } +} + +void ege_setfont(float size, const char* typeface, PIMAGE pimg) +{ + const char* validatedTypeface = typeface; + if (validatedTypeface == NULL || validatedTypeface[0] == '\0') { + validatedTypeface = "Arial"; + } + const std::wstring& wFace = mb2w(validatedTypeface); + ege_setfont(size, wFace.c_str(), pimg); +} + +void ege_setfont(float size, const wchar_t* typeface, PIMAGE pimg) +{ + ege_setfont(size, typeface, Gdiplus::FontStyleRegular, pimg); +} + +void ege_setfont(float size, const char* typeface, int style, PIMAGE pimg) +{ + const char* validatedTypeface = typeface; + if (validatedTypeface == NULL || validatedTypeface[0] == '\0') { + validatedTypeface = "Arial"; + } + const std::wstring& wFace = mb2w(validatedTypeface); + ege_setfont(size, wFace.c_str(), style, pimg); +} + +void ege_setfont(float size, const wchar_t* typeface, int style, PIMAGE pimg) +{ + PIMAGE img = CONVERT_IMAGE(pimg); + if (img) { + // Validate input parameters + const wchar_t* validatedTypeface = typeface; + if (validatedTypeface == NULL || validatedTypeface[0] == L'\0') { + validatedTypeface = L"Arial"; // Use safe default + } + + // Validate size (reasonable range for font sizes) + if (size <= 0.0f || size > 1000.0f) { + CONVERT_IMAGE_END; + return; // Invalid size, do nothing + } + + // Delete existing GDI+ font if any + if (img->m_font != NULL) { + delete img->m_font; + img->m_font = NULL; + } + + // Create GDI+ FontFamily - try requested font first, then fallbacks + Gdiplus::FontFamily* fontFamily = new Gdiplus::FontFamily(validatedTypeface); + + // If the font family is not available, try fallback fonts + if (!fontFamily->IsAvailable()) { + delete fontFamily; + + // Try Arial + fontFamily = new Gdiplus::FontFamily(L"Arial"); + if (!fontFamily->IsAvailable()) { + delete fontFamily; + + // Try SimSun (common Chinese font) + fontFamily = new Gdiplus::FontFamily(L"SimSun"); + if (!fontFamily->IsAvailable()) { + delete fontFamily; + + // Use generic sans serif as final fallback + const Gdiplus::FontFamily* genericFamily = Gdiplus::FontFamily::GenericSansSerif(); + if (genericFamily != NULL) { + fontFamily = genericFamily->Clone(); + } else { + // Last resort: return without setting font (use GDI fallback) + CONVERT_IMAGE_END; + return; + } + } + } + } + + // Validate font family is available before creating font + if (fontFamily == NULL || !fontFamily->IsAvailable()) { + if (fontFamily != NULL) { + delete fontFamily; + } + CONVERT_IMAGE_END; + return; + } + + Gdiplus::REAL cellHeight = (Gdiplus::REAL)fontFamily->GetLineSpacing(style); + Gdiplus::REAL emHeight = (Gdiplus::REAL)fontFamily->GetEmHeight(style); + // 缩放 size,去掉留白部分,得到 GDI+ 需要的纯像素大小 + Gdiplus::REAL adjustedSize = size * emHeight / cellHeight; + + Gdiplus::Font* newFont = new Gdiplus::Font(fontFamily, adjustedSize, style, Gdiplus::UnitPixel); + + // Clean up font family (Font makes its own copy) + delete fontFamily; + + // Validate the created font before storing it + if (newFont->IsAvailable()) { + img->m_font = newFont; + } else { + // Font creation failed, clean up and don't store + delete newFont; + } + } + CONVERT_IMAGE_END; } } // namespace ege diff --git a/src/image.cpp b/src/image.cpp index a5c2928..a742f9a 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -58,6 +58,7 @@ void IMAGE::reset() m_graphics = NULL; m_pen = NULL; m_brush = NULL; + m_font = NULL; #endif } @@ -184,6 +185,10 @@ int IMAGE::deleteimage() delete m_brush; } m_brush = NULL; + if (NULL != m_font) { + delete m_font; + } + m_font = NULL; #endif HBITMAP hbmp = (HBITMAP)GetCurrentObject(m_hDC, OBJ_BITMAP); diff --git a/src/image.h b/src/image.h index b010601..b2d8391 100644 --- a/src/image.h +++ b/src/image.h @@ -92,13 +92,14 @@ class IMAGE color_t m_fillcolor; color_t m_textcolor; color_t m_bk_color; - -private: #ifdef EGE_GDIPLUS Gdiplus::Graphics* m_graphics; Gdiplus::Pen* m_pen; Gdiplus::Brush* m_brush; + Gdiplus::Font* m_font; #endif + +private: bool m_aa; void initimage(HDC refDC, int width, int height); void construct(int width, int height);