You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Rust结合pdfium与WinAPI打印PDF无输出问题排查

Rust + Pdfium + WinAPI 打印PDF问题排查思路

问题描述

用Rust结合pdfium和WinAPI打印PDF,代码无报错、调试输出显示正常,但打印机队列任务仅短暂闪过无实际打印内容;用Microsoft Print to PDF测试时生成0字节且无法打开的文件,怀疑问题出在pdfium的FPDF_RenderPage方法,需排查思路。

核心排查方向

1. HDC与打印句柄未关联

当前代码分开使用CreateDCA创建HDC、OpenPrinter获取打印机句柄,但两者没有绑定,WinAPI打印流程中需要让打印文档与渲染用的HDC关联:

  • 修正方案:移除单独的OpenPrinter调用,直接用CreateDC获取的HDC调用StartDocA/StartPage(而非StartDocPrinterA/StartPagePrinter),让渲染内容直接输出到HDC对应的打印任务。

2. FPDF_RenderPage参数单位不匹配

PDF页面尺寸单位是点(1点=1/72英寸),而WinAPI HDC的单位依赖打印机分辨率(默认可能是缇或像素),直接将浮点尺寸转i32会导致渲染尺寸错误:

  • 修正步骤:
    1. 用GetDeviceCaps(hdc, LOGPIXELSX)和GetDeviceCaps(hdc, LOGPIXELSY)获取打印机DPI;
    2. 将PDF点尺寸转换为打印机像素:
      let dpi_x = GetDeviceCaps(hdc, LOGPIXELSX);
      let dpi_y = GetDeviceCaps(hdc, LOGPIXELSY);
      let render_width = (resp_page_width * dpi_x as f32 / 72.0) as i32;
      let render_height = (resp_page_height * dpi_y as f32 / 72.0) as i32;
      
    3. 打印场景建议给FPDF_RenderPage的flags参数加上0x1000(FPDF_PRINTING)标志,替换或补充当前的0x800。

3. 打印流程顺序不合理

当前在启动打印页面后才初始化pdfium、加载文档,虽不报错但可能引发资源竞争:

  • 调整顺序:将FPDF_InitLibraryWithConfig放在打印流程(StartDocPrinterA)之前,确保pdfium资源加载完成后再处理打印任务。

4. 打印数据类型不匹配

设置的data_type为XPS_PASS,但GDI渲染的内容对应RAW或系统自动判断类型:

  • 测试方案:将DOC_INFO_1A的pDatatype设为PSTR::null(),或改为"RAW\0",避免数据类型不兼容导致内容丢失。

5. 补充错误检查

现有调试仅打印基础返回值,需补充关键节点的有效性检查:

  • 检查FPDF_LoadMemDocument和FPDF_LoadPage的返回值是否为空指针;
  • WinAPI调用失败时用GetLastError()获取具体错误码,比如StartDocPrinterA返回0时排查错误原因。

原代码及调试输出

FFI 代码

[repr(C)]
#[derive(Clone, Copy)]
struct FPDF_LIBRARY_CONFIG {
    version: i32,
    m_pUserFontPaths: *const c_char,
    m_pIsolate: *const c_void,
    m_v8EmbedderSlot: u32,
}

#[cfg(target_os = "windows")]
#[link(name = "pdfium")]
extern "C" {
    fn FPDF_InitLibraryWithConfig(config: *const FPDF_LIBRARY_CONFIG) -> c_void;

    fn FPDF_DestroyLibrary() -> c_void;

    fn FPDF_LoadMemDocument(
        file_content: *const c_void,
        content_size: i32,
        password: *const c_char,
    ) -> *const c_void;

    fn FPDF_LoadPage(document: *const c_void, page_index: i32) -> *const c_void;

    fn FPDF_GetPageWidthF(page: *const c_void) -> c_float;

    fn FPDF_GetPageHeightF(page: *const c_void) -> c_float;

    fn FPDF_RenderPage(
        hdc: HDC,
        page: *const c_void,
        start_x: i32,
        start_y: i32,
        size_x: i32,
        size_y: i32,
        rotate: i32,
        flags: i32,
    ) -> c_void;
}

打印实现代码

pub fn print(file: &[u8]) -> anyhow::Result<()> {
    use std::ptr::null;

    use windows::{
        core::{s, PCSTR, PSTR},
        Win32::{
            Foundation::HANDLE,
            Graphics::{
                Gdi::{CreateDCA, DeleteDC},
                Printing::{
                    ClosePrinter, EndDocPrinter, EndPagePrinter, OpenPrinterA, StartDocPrinterA, StartPagePrinter, DOC_INFO_1A
                },
            },
        },
    };

    unsafe {
        let hdc = CreateDCA(PCSTR::null(), s!("Microsoft Print to PDF"), None, None);
        println!("create dca: {} {:?}", hdc.is_invalid(), hdc);

        let mut printer_handle = HANDLE::default();
        
        let data_type = "XPS_PASS\0"; // also tried "RAW" and "Text"

        let resp_open_printer = OpenPrinterA(s!("Microsoft Print to PDF"), &mut printer_handle, None);
        println!("open printer: {} {:?}", resp_open_printer.is_ok(), printer_handle);

        let doc_name = "Test Document\0";
        let doc_Info = DOC_INFO_1A {
            pDocName: PSTR::from_raw(doc_name.as_ptr().cast_mut()),
            pOutputFile: PSTR::null(),
            pDatatype: PSTR::from_raw(data_type.as_ptr().cast_mut()),
        };

        let resp_start_doc_printer = StartDocPrinterA(printer_handle, 1, &doc_Info);
        println!("start doc printer: {}", resp_start_doc_printer);

        let resp_start_page_printer = StartPagePrinter(printer_handle);
        println!("start page printer: {}", resp_start_page_printer.as_bool());

        let config = FPDF_LIBRARY_CONFIG {
            version: 2,
            m_pUserFontPaths: null(),
            m_pIsolate: null(),
            m_v8EmbedderSlot: 0,
        };

        FPDF_InitLibraryWithConfig(&config);

        let doc = FPDF_LoadMemDocument(file.as_ptr() as *const c_void, file.bytes().count() as i32, null());
        println!("load mem doc: {:?} {}", doc, file.bytes().count() as i32);

        let page = FPDF_LoadPage(doc, 0);
        println!("load page: {:?}", page);

        let resp_page_width = FPDF_GetPageWidthF(page);
        println!("page width: {}", resp_page_width);

        let resp_page_height = FPDF_GetPageHeightF(page);
        println!("page height: {}", resp_page_height);

        FPDF_RenderPage(hdc, page, 0, 0, resp_page_width as i32, resp_page_height as i32, 0, 0x800);

        let resp_end_page_printer = EndPagePrinter(printer_handle);
        println!("end page printer: {}", resp_end_page_printer.as_bool());

        let resp_end_doc_printer = EndDocPrinter(printer_handle);
        println!("end doc printer: {}", resp_end_doc_printer.as_bool());

        let resp_close_printer = ClosePrinter(printer_handle);
        println!("close printer: {}", resp_close_printer.is_ok());

        let resp_delete_dc = DeleteDC(hdc);
        println!("delete dc: {}", resp_delete_dc.as_bool());

        FPDF_DestroyLibrary();
    }
    
    ...
}

调试输出

create dca: false HDC(-551480207) // false is good
open printer: true HANDLE(1372067071760)
start doc printer: 73
start page printer: true
load mem doc: 0x13f780cc190 4953
load page: 0x13f758f9730
page width: 297.64 // correct width
page height: 575.43005 // correct height
end page printer: true
end doc printer: true
close printer: true
delete dc: true

内容的提问来源于stack exchange,提问作者e-Ra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 06:55:55