PBIDEA:用 uo_pdfwriter/uo_pdfview/uo_pdfmaker 生成与预览 PDF
本帖最后由 pbai 于 2026-8-28 10:42 编辑PBIDEA:用 uo_pdfwriter/uo_pdfview/uo_pdfmaker 生成与预览 PDF
阅读说明
1. 适用版本:PB 12.5 及以上(含 PBIDEA 1.x);以下 API 名称以 PBIDEA 运行库 PbIdea.dll 的导出为准
2. 支持数据库:本文不涉及数据库,无需任何 DBMS(纯文件/报表生成)
3. 操作系统与环境要求:Windows 7+,已安装 PBIDEA 运行库(PbIdea.dll),且 uo_pdfwriter/uo_pdfview/uo_pdfmaker 所在 pbl 已加入工程
4. 难度系数:★★★☆☆
5. 其它阅读说明:示例运行前须确保 PbIdea.dll 已注册(构造 uo_pdfwriter 时会自动调用 pdfwCreate);预览示例需在窗口上放置 uo_pdfview 控件;若用不可视方式调用 uo_pdfview,用后务必 Release()
一、三个对象各管一摊:先分清"谁来生成、谁来预览"
PBIDEA 在 PbIdea.dll 里封装了一组 PDF 相关对象,日常最常用的是三个,职责非常清晰:
[*]uo_pdfwriter:从零"画"出一份 PDF。你控制坐标、字体、颜色、线条、矩形、图片,逐页绘制,最终落盘成文件或内存 blob。适合生成固定版式的单据、标签、证书、报表封面等。
[*]uo_pdfmaker:把已有的 DataWindow / DataStore 通过"虚拟打印"直接导出成 PDF(也能导出 bmp/jpg/png,由扩展名决定)。适合"我已经有报表 DW,不想重画"的场景。
[*]uo_pdfview:一个窗口预览控件(继承自 userobject),负责"看" PDF——翻页、缩放、打印、另存;它也顺带提供合并(MeargePDF)、拆分(SplitPDF)、提取文本(GetPageText)等"加工"能力,但不负责从零生成。
一句话记忆:writer 负责画,maker 负责把 DW 转,view 负责看和加工。
二、uo_pdfwriter:从零绘制 PDF 的全貌
uo_pdfwriter 继承自 nonvisualobject,构造时自动调用 pdfwCreate()、析构时自动调用 pdfwDestroy(),所以你 create 之后直接开画即可,无需手写初始化/释放底层。
2.1 打开文档:数据去哪由 Open 决定
Open 有多个重载,关键点在于是否带文件名:
写法数据去向
Open(int widthMM, int heightMM)不写文件,生成的内容留在 data 成员(blob),最后用 Save(文件名) 落盘
Open(string filename, int widthMM, int heightMM)直接指定输出文件,最后 close() 即写出该文件
Open(datawindow dw) / Open(datastore ds)直接把某个 DW/DS 的当前内容渲染进 PDF
以上四种均另有带 uo_json metadata 的重载metadata 可写入 PDF 元信息(标题/作者等),配合 DrawWaterMark() 出水印
尺寸单位是毫米,A4 约为 210×297。坐标 (0,0) 在页面左上角,向右为 x 正、向下为 y 正——和屏幕坐标一致,比"纸张坐标系"好理解。
如果只想改单位(像素/英寸),可先调 SetUnit(int unit),常量:PDF_PB=0、PDF_PEXEL=1、PDF_INCH=2、PDF_MM=3(默认)、PDF_POINT=4。
2.2 页面与坐标控制
[*]BeginPage() / EndPage():开始/结束一页,可多次调用生成多页。
[*]SetMargin(left, top, right, bottom):设置页边距。
[*]SetOffset(x, y)、Translate(x, y)、Rotate(angle):平移与旋转,做复杂排版时用。
[*]SaveStatus() / RestoreStatus():保存/恢复当前绘制状态(颜色、字体、坐标变换),类似画笔的"快照"。
2.3 文本绘制
SetFont(fontName, fontSize) 或 SetFont(fontName, fontSize, bold, italic) 设定字体;SetColor(ulong clr)(clr 用 PB 的 RGB(r,g,b) 生成)或 SetColor(clr, alpha) 设定颜色。然后 DrawString 有 6 个重载,常用的是:
[*]DrawString(text):在当前位置写文本;
[*]DrawString(x, y, text):在指定坐标写文本;
[*]DrawString(x, y, width, height, text, align):在指定矩形框内写文本,align 用对齐常量(见 2.5)。
2.4 图形与图片
[*]Rectangle(left, top, width, height, stroke, fill):画矩形,stroke=是否描边、fill=是否填充;
[*]LineTo(x1, y1, x2, y2, stroke):画线;
[*]Stroke() / Fill() / FillAndStrok():对未立即描边/填充的图形后续补操作;
[*]DrawPicture(x, y, width, height, imageFile) 或 DrawPicture(x, y, width, height, imageData):插图,支持 bmp/jpeg/png,可传文件名也可传图片 blob;另有带 alpha(透明度 0-255)和 angle(旋转 0-360)的重载。
2.5 常用常量(类型变量)
[*]单位:PDF_MM=3 等(见 2.1);
[*]文本对齐:ALIGN_LEFT=0、ALIGN_CENTER=1、ALIGN_RIGHT=2、ALIGN_TOP=0、ALIGN_VCENTER=4、ALIGN_BOTTOM=8;
[*]线型:LINE_SOLID=0、LINE_DASH=1、LINE_DOT=2、LINE_DASHDOT=3、LINE_DASHDOTDOT=4。
三、典型用法示例(最小可运行)
示例 1:用 uo_pdfwriter 生成一份带标题、线条和图片的 PDF
前置:新建窗口 w_pdf_demo,放一个按钮 cb_make。工程已加入含 uo_pdfwriter 的 pbl,且 PbIdea.dll 已注册。运行后会在 C:\temp 下生成 demo.pdf。
// 示例输入:输出 PDF 文件名(含路径)
string ls_file
ls_file = 'C:\temp\demo.pdf'
// 示例输入:要插入的图片(支持 bmp/jpeg/png),不存在则跳过图片
string ls_img
ls_img = 'C:\temp\logo.png'
// 示例输入:页面尺寸(毫米),A4 约 210 x 297
int li_w, li_h
li_w = 210
li_h = 297
uo_pdfwriter lw
lw = create uo_pdfwriter
// 打开文档:直接带文件名 + 尺寸(毫米),Close 时自动落盘
lw.Open(ls_file, li_w, li_h)
// 新页:坐标 (0,0) 在左上角,单位默认毫米
lw.BeginPage()
// 标题:宋体 16 号,黑色
lw.SetFont('宋体', 16)
lw.SetColor(RGB(0, 0, 0))
lw.DrawString(20, 20, 'PowerBuilder 生成的 PDF 示例')
// 正文:12 号字
lw.SetFont('宋体', 12)
lw.DrawString(20, 40, '这是用 uo_pdfwriter 从零绘制的一段正文。')
// 画一条横线(stroke=true 立即描边)
lw.LineTo(20, 60, 190, 60, true)
// 画一个蓝色填充矩形(stroke=false 不描边,fill=true 填充)
lw.SetColor(RGB(0, 0, 255))
lw.Rectangle(20, 70, 60, 8, false, true)
// 插入图片(文件存在才画)
if FileExists(ls_img) then
lw.DrawPicture(20, 90, 80, 40, ls_img)
end if
lw.EndPage()
// 关闭并落盘(Open 已带文件名,Close 直接写出)
lw.close()
MessageBox('完成', 'PDF 已生成:' + ls_file)
示例 2:把 DataWindow 直接导出为 PDF(uo_pdfmaker)
前置:窗口上已放置 DataWindow 控件 dw_1,DataObject 设为报表 d_report,且已 Retrieve 取数。
// 前置:窗口上已放置 DataWindow 控件 dw_1(DataObject = d_report),且已 Retrieve 取数
// 示例输入:输出 PDF 文件名
string ls_pdf
ls_pdf = 'C:\temp\report.pdf'
uo_pdfmaker lm
lm = create uo_pdfmaker
// 虚拟打印 DW 内容为 PDF;扩展名 pdf 决定输出类型(也可 bmp/jpg/png)
boolean lb_ok
lb_ok = lm.PrintDatawindow(dw_1, ls_pdf)
if lb_ok then
MessageBox('成功', '报表已导出:' + ls_pdf)
else
MessageBox('失败', '导出失败,请检查 PbIdea.dll 与虚拟打印机配置')
end if
PrintDatawindow 还有不带文件名的版本 PrintDatawindow(dw) 直接返回 blob,方便上传或入库,不必落盘。
示例 3:在窗口里预览 PDF(uo_pdfview 控件)
前置:窗口 w_pdfview 上放置 uo_pdfview 控件 uo_pdfview_1(从 userobject 拖入即可)。
// 前置:窗口 w_pdfview 上已放置 uo_pdfview 控件 uo_pdfview_1
// 示例输入:要预览的 PDF 文件路径
string ls_file
ls_file = 'C:\temp\demo.pdf'
boolean lb_ok
lb_ok = uo_pdfview_1.Load(ls_file)
if lb_ok then
MessageBox('提示', 'PDF 已加载,可滚动/翻页/缩放浏览')
else
MessageBox('失败', '加载失败,文件可能不存在或已损坏')
end if
预览控件常用事件:onready(integer pagecount) 在加载完成时触发,可在里面显示总页数;onpagechanged(integer page) 在翻页时触发;onclose() 在关闭时触发;oncommand(long id) 返回 false 走工具栏默认动作、返回 true 则由你自己接管对应按钮。
示例 4:合并多个 PDF(注意函数名是 MeargePDF)
前置:准备若干 PDF 文件,准备把它们的内容按顺序合并成一个新文件。
// 示例输入:待合并的 PDF 文件数组
string ls_files[]
ls_files = 'C:\temp\a.pdf'
ls_files = 'C:\temp\b.pdf'
ls_files = 'C:\temp\c.pdf'
// 示例输入:合并后的输出文件
string ls_out
ls_out = 'C:\temp\merged.pdf'
uo_pdfview lv
lv = create uo_pdfview
// 注意:源码里函数名拼写为 MeargePDF(不是 MergePDF),调用必须照写
boolean lb_ok
lb_ok = lv.MeargePDF(ls_files, ls_out)
// 不可视方式使用时必须 Release(),否则会内存泄漏
lv.Release()
if lb_ok then
MessageBox('完成', '已合并为:' + ls_out)
else
MessageBox('失败', '合并失败')
end if
四、边界情况与常见坑(务必留意)
[*]MeargePDF 不是 MergePDF:源码导出名就是 MeargePDF(注意是 Merge 的 M-e 之后多写了一个 a,g、e 数量都一致),调用时照搬,别自作主张改成"正确"的 Merge,否则编译找不到函数。
[*]Open 带不带文件名,决定数据去向:
[*]Open(file, w, h) → 最后 close() 直接写出该文件;
[*]Open(w, h) 不带文件名 → 内容留在 lw.data(blob 成员),必须再调 lw.Save(文件名) 才能落盘,close() 不会写文件。两者不要混用。
[*]坐标与单位:默认单位是毫米,(0,0) 在左上角。需要像素/英寸先 SetUnit。绘制前务必 BeginPage,否则内容不进任何页。
[*]中文与字体:SetFont 用系统已安装的字体名,例如 '宋体' 或 'SimSun'。字体名写错、或目标机器没装该字体,中文会不显示或变方块。
[*]uo_pdfview 两种用法:放在窗口上做预览,生命周期随窗口自动管理;用 create uo_pdfview 以不可视方式做合并/拆分/提取文本时,用后必须 Release(),否则内存泄漏。
[*]图片格式:DrawPicture 支持 bmp/jpeg/png。传 blob 时要传图片的原始字节(可用 uo_file.BlobFromFile 之类读入),不是文件路径字符串。
[*]依赖 PbIdea.dll:uo_pdfwriter 一构造就 pdfwCreate(),若 dll 未注册、不在 PATH、或位数不匹配(32/64 位 PB 与 dll 要一致),构造即失败。部署机器必须随包带上该 dll。
[*]判返回值:Open/Save/PrintDatawindow/MeargePDF 等都返回 boolean,生成前养成判返回值的习惯,失败优先结合 PbIdea.dll 状态排查,而不是盲目重试。
五、与相近方案对比
方案适用场景优点代价
uo_pdfwriter 手工绘制固定版式单据/标签/封面体积小、像素级可控、不依赖已有 DW版式要自己算坐标,复杂报表难写
uo_pdfmaker DW 虚拟打印已有报表 DW 想导出零重画,直接复用现有报表样式受 DW 限制,难做 DW 之外的自由排版
uo_pdfview 浏览/加工看 PDF、合并/拆分/取文本控件即开即用,加工 API 全只"看和改",不能从零生成
第三方(GhostScript/商业库)需要表单域、加密、数字签名等高级特性能力最全需额外安装/授权
实际项目常组合使用:用 maker 把报表 DW 导出,用 view 合并多份并加水印,用 writer 补一张定制封面,最后再合并成一个成品 PDF。
六、进阶扩展点
[*]水印:uo_pdfmaker.SetWaterMaker(text, color, angle[, alpha]) 给导出的 PDF 加文字水印;uo_pdfwriter 则在 Open 时传入带水印信息的 uo_json metadata,再调 DrawWaterMark() 绘制。
[*]PDF 元信息:uo_pdfwriter.Open(文件名, 宽, 高, metadata) 的 metadata 可写标题/作者,最终体现在 PDF 属性里。
[*]提取文本做发票识别:uo_pdfview.GetPageText(pageIndex, uo_json result, offsetX, offsetY) 按字间距/行间距把散文本聚合成句,适合做结构化抽取;GetPageText(page, ref sText[], ref strPosition[]) 还能拿到每个文本块的坐标。
[*]打印与另存:uo_pdfview.Print(显示对话框?) / Print(打印机名) / Print(uo_json 选项),SaveAs(新文件) / SaveAsBlob()。
[*]拆分:uo_pdfview.SplitPDF(源文件, 前缀, ref 输出数组[]),还支持按书签(byBookmark)或按关键字坐标拆分。
[*]后台 DW 转 blob:uo_pdfmaker.PrintDatawindow(dw) 返回 blob,可直接入库或网络上传,不必落盘。
掌握这三件套,PowerBuilder 项目里"生成报表 PDF / 预览单据 / 合并归档"这类需求基本都能一站式搞定,无需再外接复杂的第三方库。 MeargePDF 不是 MergePDF:源码导出名就是 MeargePDF(拼写少了一个 g)
是少了一个 a吧 @woyaodwn 多谢挑刺,这点确实是我写错了,更正一下:
[*]源码导出名确为 MeargePDF(不是 MergePDF)——uo_pdfview.sru 里的声明是 function boolean MeargePDF(...) library "PbIdea.dll" alias for "pdfMeargePDF",所以调用时必须照写 MeargePDF,自作主张改成 Merge 反而编译不过。这一点原文是对的。
[*]但我括号里的解释写错了:我说"拼写少了一个 g"。其实 Merge(M-e-r-g-e)和 Mearge(M-e-a-r-g-e)g 的数量一样,差别在 e 被误写成了 a——Mearge 比 Merge 多一个 a、少一个 e。正确说法是"把 e 错写成了 a"。
已记下这个坑,后面的文章会改对措辞。再次感谢 👍 @woyaodwn 再补一刀,我上一条回复那句还是没写准,重新钉死真正原因:
问题出在字母 a,不是 e、也不是 g:
[*]源码导出名是 MeargePDF(不是 MergePDF)。
[*]把 MergePDF 和 MeargePDF 逐字母比,两者 唯一的差别是 MeargePDF 多了一个 a(插在第一个 e 之后);g、e 的数量都和 MergePDF 完全一致。
[*]所以站在"正确拼法" MergePDF 的角度看,它确实"少了一个 a"——你这句说对了。
我前面两处都写错了:原文括号里说"少了一个 g"错;我上一条改口成"Mearge 比 Merge 多一个 a、少一个 e"也错(e 的数量根本没变)。在此更正:差异只有"多一个 a",没有"少一个 e"。这个坑已记下,谢谢 again 👍
页:
[1]