手搓代码,运行良好,控制台、服务类程序同样适用。
基本原理是 RTF 文件利用 RICHEDIT 控件的打印功能,打印到图片,再使用 pdflib 库,转换成 PDF.。
那么问题来了:
1、由于使用了 RICHEDIT 控件,就只能在 Windows 下使用,不能跨平台,有没有可以类似代码不需要经过 RICHEDIT 控件的。
2、有没有不使用 pdflib 库的代码,毕竟 pdflib 虽然对个人免费,但对商业使用是收费的。
/*
#include "PDFLib.h"
#ifdef _M_X64
#pragma comment(lib, "..\\lib\\x64\\PDFLib.lib")
#else
#pragma comment(lib, "..\\lib\\x86\\PDFLib.lib")
#endif
#include <atlimage.h>
*/
///<summary>RTF 转 PDF</summary>
///<param name="sRtf">RTF内容</param>
///<param name="fPaperW">纸张宽度 mm</param>
///<param name="fPaperH">纸张高度 mm</param>
///<param name="nMarginL">纸张左边距 mm</param>
///<param name="nMarginT">纸张上边距 mm</param>
///<param name="nMarginR">纸张右边距 mm</param>
///<param name="nMarginB">纸张下边距 mm</param>
///<returns>PDF 内容</returns>
std::string Rtf2Pdf(std::string sRtf, double fPaperW, double fPaperH, int nMarginL, int nMarginT, int nMarginR, int nMarginB)
{
if (fPaperW <= 0 || fPaperH <= 0)
return "";
::CoInitialize(NULL); // 初始化 COM 库
ULONG_PTR uGdiplusToken = 0;
Gdiplus::GdiplusStartup(&uGdiplusToken, &Gdiplus::GdiplusStartupInput({ 0 }), NULL); // 初始化 GDI++
HMODULE hLibRich = ::LoadLibrary(L"Msftedit.dll"); // 加载 RichEdit 库
HWND hWndRich = ::CreateWindow(MSFTEDIT_CLASS, L"", ES_MULTILINE, 0, 0, 0, 0, NULL, NULL, ::GetModuleHandle(NULL), NULL); // 创建打印使用的 RichEdit 控件
int nPxPaperW = MM2PX(fPaperW, 96); // 像素宽度,#define MM2PX(mm, dpi) int((mm) * dpi / 25.4) ,mm 转换成 px (像素),需要提供当前分辨率,一般是 96
int nPxPaperH = MM2PX(fPaperH, 96); // 像素高度,#define MM2PX(mm, dpi) int((mm) * dpi / 25.4) ,mm 转换成 px (像素),需要提供当前分辨率,一般是 96
std::string sPdf;
if (::SetEditRtfCode(hWndRich, sRtf))
{
std::deque<CHARRANGE> lsPage; // 每页的开始位置和结束位置
lsPage.push_back(CHARRANGE{ 0, -1 }); // 新一页位置
FINDTEXTEX ftPage = { { 0, -1 }, L"[PAGEBREAK]", { -1, -1 } }; // [PAGEBREAK] 为分页标记
while ((long)::SendMessage((HWND)hWndRich, EM_FINDTEXTEX, FR_DOWN, (LPARAM)&ftPage) != -1)
{
lsPage.back().cpMax = ftPage.chrgText.cpMin; // 上一页结束位置,不包括 [PAGEBREAK]
// 下一页开始位置跳过分页标记后面的一个空格回车换行类字符
CHARRANGE crBlank = { ftPage.chrgText.cpMax, ftPage.chrgText.cpMax + 1 };
::SendMessage((HWND)hWndRich, EM_EXSETSEL, 0, (LPARAM)&crBlank);
if (::SendMessage((HWND)hWndRich, EM_SELECTIONTYPE, 0, 0) == SEL_TEXT)
{
wchar_t sSelText[8] = { 0 };
::SendMessage((HWND)hWndRich, EM_GETSELTEXT, 0, (LPARAM)sSelText);
if (___isblank(sSelText[0]))
ftPage.chrgText.cpMax++;
}
lsPage.push_back(CHARRANGE{ ftPage.chrgText.cpMax, -1 }); // 新一页位置,不包括 [PAGEBREAK]
ftPage.chrg.cpMin = ftPage.chrgText.cpMax; // 从下一个位置继续查找
}
// 创建内存 PDF 文件
PDF* pdf = ::PDF_new();
::PDF_begin_document(pdf, "", 0, "");
std::deque<unsigned char> sImgData(nPxPaperW * nPxPaperH * 4, 0);
CImage img;
img.Create(nPxPaperW, nPxPaperH, 32);
HDC hImgDC = img.GetDC();
for (CHARRANGE& chrg : lsPage)
{
COLORREF cBkOld = ::SetBkColor(hImgDC, RGB(255, 255, 255)); // 背景
RECT rPxPage = { 0, 0, nPxPaperW, nPxPaperH };
::ExtTextOut(hImgDC, 0, 0, ETO_OPAQUE, &rPxPage, NULL, 0, NULL); // 填充白色背景
::SetBkColor(hImgDC, cBkOld); // 背景
FORMATRANGE fr = { hImgDC, hImgDC };
fr.rc = fr.rcPage = { 0, 0, MM2TW(fPaperW), MM2TW(fPaperH) }; // #define MM2TW(mm) int((mm) * 14400 / 254),mm 转换成 twips (缇)
fr.rc.left += MM2TW(nMarginL); // 纸张左边距 twip,#define MM2TW(mm) int((mm) * 14400 / 254),mm 转换成 twips (缇)
fr.rc.top += MM2TW(nMarginT); // 纸张上边距 twip,#define MM2TW(mm) int((mm) * 14400 / 254),mm 转换成 twips (缇)
fr.rc.right -= MM2TW(nMarginR); // 纸张右边距 twip,#define MM2TW(mm) int((mm) * 14400 / 254),mm 转换成 twips (缇)
fr.rc.bottom -= MM2TW(nMarginB); // 纸张下边距 twip,#define MM2TW(mm) int((mm) * 14400 / 254),mm 转换成 twips (缇)
fr.chrg = chrg;
::SendMessage(hWndRich, EM_FORMATRANGE, TRUE, (LPARAM)&fr); // 输出到图像
IStream* pIStream = NULL;
HGLOBAL hGlobal = ::GlobalAlloc(GHND, 0); // 创建可移动的缓冲区
::CreateStreamOnHGlobal(hGlobal, FALSE, &pIStream); // 创建 IStream 流
img.Save(pIStream, Gdiplus::ImageFormatPNG); // 保存到 IStream 流
void* pLocalBuf = ::GlobalLock(hGlobal); // 缓冲区的起始地址
int nSizeInByte = (int)::GlobalSize(hGlobal); // 缓冲区的字节长度
::PDF_begin_page_ext(pdf, fPaperW, fPaperH, ""); // 页面
std::string sPvfFile = W2M(GetRandomMd5().c_str()); // 虚拟文件名
::PDF_create_pvf(pdf, sPvfFile.c_str(), 0, pLocalBuf, nSizeInByte, "");
int nPdfImg = ::PDF_load_image(pdf, "auto", sPvfFile.c_str(), 0, "");
assert(nPdfImg != -1);
::GlobalUnlock(hGlobal);
::GlobalFree(hGlobal);
pIStream->Release();
if (nPdfImg != -1)
{
::PDF_fit_image(pdf, nPdfImg, 0, 0, "adjustpage");
::PDF_close_image(pdf, nPdfImg);
}
::PDF_delete_pvf(pdf, sPvfFile.c_str(), 0);
::PDF_end_page_ext(pdf, ""); // 结束本页
}
::SendMessage(hWndRich, EM_FORMATRANGE, FALSE, 0); // 释放缓存
::PDF_end_document(pdf, "");// 关闭PDF文件
long nPdfBuf = 0;
const char* sPdfBuf = ::PDF_get_buffer(pdf, &nPdfBuf);
sPdf.assign(sPdfBuf, nPdfBuf);
::PDF_delete(pdf);
img.ReleaseDC();
}
::DestroyWindow(hWndRich); // 销毁 RichEdit
::FreeLibrary(hLibRich); // 卸载 RichEdit 库
Gdiplus::GdiplusShutdown(uGdiplusToken); // 结束 GDI++
::CoUninitialize(); // 终止 COM 库
return sPdf;
}