GDI+ DrawString打印PDF/PNG时无法绘制水印问题排查
Hook了EndPage()函数,使用GDI+的DrawString为打印的JPG、DOCX、PNG、PDF文档添加水印。DOCX和JPG格式下水印正常显示,但PDF和PNG格式完全看不到水印。已确认通过RPC获取的水印参数在所有格式下均正确,附上代码请求排查原因:
int EndPageHook::hookFunction(HDC hdc) { //Intialize GdiplusStartupInput and Graphics Gdiplus::GdiplusStartupInput gdiplusStartupInput; Gdiplus::GdiplusStartup(&m_gdiplusToken, &gdiplusStartupInput, NULL); Graphics graphic_Obj(hdc); //Get Watermark Information Throught RPC WatermarkParameters = WaterMarkRPCClient::GetWaterMarkInfo(status, printInfo); //Get Watermark Information and set in local variables string s = std::to_string(WatermarkParameters.transparency); s = std::to_string(WatermarkParameters.positions); s = std::to_string(WatermarkParameters.font); //Concatenation Text + User + Ip + Date wcsncat_s(finalsenetce, WatermarkParameters._text, 512); if (WatermarkParameters.Has_UserName) { wcsncat_s(finalsenetce, WatermarkParameters._username, 256); } if (WatermarkParameters.Has_Client) { wcsncat_s(finalsenetce, WatermarkParameters._client, 256); } if (WatermarkParameters.Has_Date) { wcsncat_s(finalsenetce, WatermarkParameters._date, 256); } { //Set watermark position StringAlignment WMLocation{}; StringFormat stringFormat; switch (WatermarkParameters.positions) { case 1: stringFormat.SetAlignment(StringAlignment::StringAlignmentNear); stringFormat.SetLineAlignment(StringAlignment::StringAlignmentNear); break; case 2: stringFormat.SetAlignment(StringAlignment::StringAlignmentCenter); stringFormat.SetLineAlignment(StringAlignment::StringAlignmentCenter); break; case 3: stringFormat.SetAlignment(StringAlignment::StringAlignmentFar); stringFormat.SetLineAlignment(StringAlignment::StringAlignmentFar); break; default: stringFormat.SetAlignment(StringAlignment::StringAlignmentCenter); stringFormat.SetLineAlignment(StringAlignment::StringAlignmentCenter); break; } // Claculate Page Size double WidthsPixels = GetDeviceCaps(hdc, VERTRES); double HeightsPixels = GetDeviceCaps(hdc, HORZRES); double WidthsPixelsPerInch = GetDeviceCaps(hdc, LOGPIXELSX); double HeightsPixelsPeInch = GetDeviceCaps(hdc, LOGPIXELSY); ////---------------------------------------------------------------------------------------------------------------- Gdiplus::FontFamily nameFontFamily(L"Arial"); Gdiplus::Font font(&nameFontFamily, WatermarkParameters.font, Gdiplus::FontStyleBold, Gdiplus::UnitPoint); Gdiplus::RectF rectF(0, 0, 20, 20); Gdiplus::SolidBrush solidBrush(Gdiplus::Color(WatermarkParameters.transparency, 250, 0, 250)); rectF.Width = (HeightsPixels / HeightsPixelsPeInch)* Screendpi; rectF.Height = (WidthsPixels / WidthsPixelsPerInch)* Screendpi; graphic_Obj.DrawString(finalsenetce, -1, &font, rectF, &stringFormat, &solidBrush); } GdiplusShutdown(m_gdiplusToken); return getOriginalFunction()(hdc); }
可能的原因及修复方案
1. GDI+初始化/销毁时机错误
每次EndPageHook调用都重复执行GdiplusStartup和GdiplusShutdown,会导致GDI+状态异常,尤其在PDF/PNG这类依赖特殊打印驱动的场景下。正确做法是:
- 在Hook初始化时仅调用一次
GdiplusStartup - 在Hook卸载时仅调用一次
GdiplusShutdown - 不要在每次
EndPage回调内重复执行这两个操作
2. 页面尺寸参数搞反
代码中VERTRES和HORZRES的取值逻辑错误:
// 错误:VERTRES是垂直像素数(页面高度),HORZRES是水平像素数(页面宽度) double WidthsPixels = GetDeviceCaps(hdc, VERTRES); double HeightsPixels = GetDeviceCaps(hdc, HORZRES);
这会导致rectF宽高计算完全颠倒,水印被绘制到页面可视区域外。修正为:
double pageWidthPixels = GetDeviceCaps(hdc, HORZRES); double pageHeightPixels = GetDeviceCaps(hdc, VERTRES);
3. Screendpi变量未定义/取值错误
代码中使用的Screendpi未看到定义和赋值,若该值与当前打印DC的DPI不匹配,会导致rectF尺寸错误,水印超出页面范围。建议直接使用打印DC自身的DPI计算:
rectF.Width = pageWidthPixels; rectF.Height = pageHeightPixels;
或确保Screendpi赋值为当前打印DC的LOGPIXELSX/LOGPIXELSY。
4. 目标字符串未初始化
wcsncat_s(finalsenetce, WatermarkParameters._text, 512)中,若finalsenetce未初始化为空字符串,首次拼接会因为缓冲区无\0终止符导致未定义行为,在PDF/PNG打印场景下可能直接绘制失败。需提前初始化:
wchar_t finalsenetce[1024] = {0}; // 分配足够大的缓冲区并清零
5. 字体加载失败
FontFamily nameFontFamily(L"Arial")若打印驱动中不存在Arial字体,会导致字体加载失败,DrawString无输出。可替换为系统内置、打印驱动必支持的字体,或增加可用性检查:
Gdiplus::FontFamily nameFontFamily(L"Arial"); if (!nameFontFamily.IsAvailable()) { // 字体不可用时 fallback 到默认字体 nameFontFamily = Gdiplus::FontFamily(L"Segoe UI"); }
6. 打印DC兼容性问题
部分PDF/PNG打印驱动的DC可能不兼容GDI+操作,可尝试保存并恢复DC状态:
int savedDC = SaveDC(hdc); // 执行GDI+绘制逻辑 graphic_Obj.DrawString(...); RestoreDC(hdc, savedDC);
内容的提问来源于stack exchange,提问作者Mohammadreza Hojatoleslami

