【QT特性技术讲解】QPrinter、QPdf

前言

QT对打印和PDF应用场景,做了简单的封装,复杂的功能还是得用第三方库,打印功能简单的文本可以不用PDF,涉及图形的基本都要用到PDF。

Linux打印

随着国产信创项目替换基于Linux的桌面系统国产信创系统,Linux桌面系统用的装机量越来越多,打印功能的使用频度也越来越高,以下分享Linux下的打印工具。

CPUS

​​CUPS​​(​​Common UNIX Printing System​​,通用UNIX打印系统)是 Linux 和类 UNIX 操作系统上事实标准的、开源的打印系统。在 2007 年被苹果公司收购。苹果macOS的打印系统也是基于 CUPS(Quartz+CUPS,Quartz将内容渲染为PDF格式‌,再推送到CUPS进行打印)。

国产信创操作系统(麒麟kylin和统信UOS系统)默认安装了CUPS,可以提供web、官方工具或者是第三方工具查看系统打印机信息。

web访问

默认端口是631,创建打印机需要用户名和密码。

复制代码
http://localhost:631/

在上图中可以看到Local Printers中有一个CUPS-PDF,这是把打印内容输出为PDF文件,CUPS默认是不安装这个功能的,需要手动下载

复制代码
sudo apt install cups-pdf

cups-pdf

配置文件:/etc/cups/cups-pdf.conf

配置文件中Out ${HOME}/PDF为pdf文件保存的路径,可以修改为:Out /home/printpool

对于新手来说,web方式不是很好操作,以下介绍好用的工具。

system-config-printer

复制代码
apt install system-config-printer

以下展示如何创建一个PD打印机

以上配置完成之后,打开一个待打印的文件,点击打印按钮之后,即可看到新增的打印机,如下图

应用场景延伸

以上PDF打印机是可以创建很多的,但cups-pdf配置文件指向了统一的输出目录,如果需要多个cups-pdf,多个不同的输出目录,可以如下操作:

在/etc/cups目录下拷贝cups-pdf.conf,比如生成cups-pdf-mypdf.conf,修改cups-pdf-mypdf中Out的输出目录即可,这个时候在web端我打马赛克的地方可以看到这个PDF,但在system-config-printer工具中是不到的,不管没关系,创建过程是一样的,只需要修改设备URI即可,如下

命令行工具

lpstat -p -d: 列出所有打印机 (-p) 并显示默认打印机 (-d)

lp <文件名>: 使用默认打印机打印文件

lp -d <打印机名> <文件名>: 指定打印机进行打印。

sudo lpadmin -p <打印机名称> <关键参数>:创建打印机

复制代码
sudo lpadmin -p MyPrinter -E -v ipp://192.168.1.100/ipp/port1 -m everywhere
连接类型​ ​URI格式​ ​示例​
PDF打印机 cups-pdf:/ cups-pdf:/
USB打印机 usb://vendor/model?serial=xxx usb://HP/Deskjet?serial=123ABC
网络IPP打印机 ipp://IP地址/ipp/print ipp://192.168.1.100/ipp/print
Windows共享打印机 smb://用户名:密码@主机名/打印机共享名 smb://user:pass@WINPC/OfficePrinter
LPD协议打印机 lpd://IP地址/队列名 lpd://192.168.1.101/L1
AppSocket打印机 socket://IP地址:端口 socket://192.168.1.102:9100
以ppd模板创建打印机

ppd模板文件默认生成在/etc/cups/ppd/目录下,上面通过system-config-printer创建的【我的PDF打印机】在此目录下会生成一个【我的PDF打印机.ppd】文件,以此作为模板即可生成配置一样的打印机,如下

复制代码
sudo lpadmin -p 我的打印机 -E -v cups-pdf:/ -P /etc/cups/ppd/我的PDF打印机.ppd

cups开发库

复制代码
sudo apt install cups libcups2-dev

编译时加上-lcups

复制代码
//列出所有打印机
#include <cups/cups.h>

int main() {
    cups_dest_t *dests;
    int num_dests = cupsGetDests(&dests);  // 获取打印机列表

    printf("找到 %d 台打印机:\n", num_dests);
    for (int i = 0; i < num_dests; i++) {
        printf("%d. %s", i+1, dests[i].name);
        if (dests[i].is_default) 
            printf(" [默认]");
        printf("\n");
    }

    cupsFreeDests(num_dests, dests);  // 释放内存
    return 0;
}

以下是核心API

​函数​ ​描述​
cupsGetDests() 获取打印机列表
cupsPrintFile() 提交文件打印任务
cupsGetDefault() 获取默认打印机名称
cupsLastErrorString() 获取最后一次错误的描述
cupsAddOption() 添加打印选项
cupsGetPPD() 获取打印机的 PPD 文件路径
cupsTempFile() 创建临时文件
cupsMarkOptions() 验证打印选项是否有效

window打印

传统打印路径依赖 ‌GDI‌(图形设备接口),较老旧,现代路径使用 ‌XPS‌(XML 打印规范,类似 PDF)。打印驱动直接与 GDI/XPS 交互。从这里可以看出window下与Linux和macOS完全不一样,QT的接口封装也不一样,可能也是QT没有对QPrinter进行深度的封装的原因吧。

在国产信创项目推进过程中,很多客户都会问:我的这些打印机都能在国产信创终端上使用吗。第一反应通常是:打印机厂商提供有驱动的都可以用(很多老打印机厂商是不维护驱动的,这部分打印机无法在国产信创设备下使用)。但换个思路,提供一台有老打印机驱动的window系统,结合以上分享的linux的cups-pdf打印机,把PDF文件传输到这个window系统下生成打印任务,即可解决"国产信创设备下使用不了老打印机"的问题。

window下,QT的QPrinter支持调用系统级打印对话框来打印PDF文件,但不支持后台命令行方式(打印机名称+PDF文件名称)打印PDF文件的!以下分享一个支持后台命令行方式打印PDF文件的工具。

SumatraPDF

下载地址:Sumatra PDF reader download pagehttps://www.sumatrapdfreader.org/download-free-pdf-viewer配置环境变量之后,打印PDF文件的命令行如下

复制代码
SumatraPDF.exe -print-to "Lexmark MX410de" "D:\myfile.pdf" > nul 2>&1

以上的分享也是希望点题:print和pdf分不开。 以下会分享一个qt样例,从开发角度进一步了解两者的关系:选择并展示PDF内容,打印预览PDF内容,打印PDF内容。

效果图

功能详细讲解

​​​​ QPdf核心接口是QPdfDocuments,可进行加载PDF、获取PDF页数、将 PDF 页面渲染为图像。

选择PDF文件

调用了加载PDF、获取PDF页数两个接口,代码如下:

复制代码
//头文件
#include <QPdfDocument>
class MainWindow : public QMainWindow
{
private:
    QPdfDocument *pdfDoc;
};

//cpp文件
#include <QStandardPaths>
void MainWindow::on_btnSelect_clicked()
{
    QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);//获取桌面路径
    QString file = QFileDialog::getOpenFileName(this,tr("Open PDF"), desktopPath, tr("PDF Files (*.pdf)"));
    if(file.isEmpty()) return;

    currentFile = file;
    pdfDoc->load(file);
    ui->spinBox->setMaximum(pdfDoc->pageCount());
    ui->spinBox->setValue(1);
    updatePreview();
}

void MainWindow::updatePreview()
{
    if(pdfDoc->pageCount() <= 0) return;
    int page = ui->spinBox->value() - 1;
    QImage image = pdfDoc->render(page, ui->labelPreview->size());
    ui->labelPreview->setPixmap(QPixmap::fromImage(image));
}

打印预览

QPdf部分调用了获取PDF页数、将 PDF 页面渲染为图像两个接口

QPrinter部分调用了QPrintPreviewDialog弹出系统级的打印对话框,对话框上有打印按钮,另外QPrintPreviewWidget可以显示打印预览框,但是风格比较简洁,效果如下:

以下仅给出使用QPrintPreviewDialog的代码:

复制代码
//头文件
#include <QPainter>
class MainWindow : public QMainWindow
{
private:
    QPrinter printer;
}

//cpp文件
void MainWindow::on_actionPrintPreview_clicked()
{
    if (pdfDoc->pageCount() == 0) return; // 检查是否有可打印内容

    // 创建打印预览部件
    QPrintPreviewDialog preview(&printer, this);
    connect(&preview, &QPrintPreviewDialog::paintRequested,
            this, &MainWindow::printPreview);
    preview.exec();
}

void MainWindow::printPreview()
{

    QPainter painter(&printer);
    painter.setRenderHints(QPainter::Antialiasing |
                               QPainter::TextAntialiasing |
                               QPainter::SmoothPixmapTransform,// 使用平滑变换绘制
                           true);
    // 精确计算DPI缩放比例
    const double dpiScale = printer.logicalDpiX() / 72.0;
    painter.scale(dpiScale, dpiScale);

    // 高质量渲染PDF每页内容
    for(int i = 0; i < pdfDoc->pageCount(); ++i) {
        if(i > 0) printer.newPage();

        QSizeF pageSize = pdfDoc->pagePointSize(i);
        // 将PDF页面渲染为图像
        QImage pageImage = pdfDoc->render(i, pageSize.toSize() * dpiScale);
        // 绘制到打印机
        painter.drawImage(QRectF(0, 0, pageSize.width(), pageSize.height()),
                          pageImage,
                          QRectF(0, 0, pageImage.width(), pageImage.height()));
    }
}

打印文件

QPdf部分调用了获取PDF页数、将 PDF 页面渲染为图像两个接口

QPrinter部分调用了QPrintDialog弹出系统级的打印对话框,让用户选择打印机之后进行打印

复制代码
void MainWindow::on_btnPrint_clicked()
{
    if(currentFile.isEmpty()) {
        QMessageBox::warning(this, tr("Error"), tr("No PDF file selected"));
        return;
    }

    QPrintDialog dialog(&printer, this);
    if(dialog.exec() == QDialog::Accepted) {
        QPainter painter;
        if(!painter.begin(&printer)) {
            QMessageBox::critical(this, tr("Error"), tr("Failed to initialize printer"));
            return;
        }

        for(int i = 0; i < pdfDoc->pageCount(); ++i) {
            if(i > 0) printer.newPage();
            //QImage image = pdfDoc->render(i, printer.pageRect(QPrinter::Point).size());
            QSizeF sizeF = printer.pageRect(QPrinter::Point).size();
            QImage image = pdfDoc
                               ->render(i, sizeF.toSize());  // 显式转换为QSize
            painter.drawImage(printer.pageRect(QPrinter::Point), image);
        }
        painter.end();
    }
}
相关推荐
ajassi20008 小时前
开源 C++ QT Widget 开发(十一)进程间通信--Windows 窗口通信
linux·c++·windows·qt·开源
2401_858286118 小时前
CD75.【C++ Dev】异常
开发语言·c++·异常
魔力之心9 小时前
R notes[2]
开发语言·r语言
再努力"亿"点点9 小时前
炫酷JavaScript鼠标跟随特效
开发语言·前端·javascript
正义的大古9 小时前
OpenLayers 入门篇教程 -- 章节三 :掌控地图的视野和交互
开发语言·vue.js
pythonpapaxia10 小时前
Java异常处理:掌握优雅捕获错误的艺术
java·开发语言·python·其他
l1t10 小时前
利用美团longcat.ai编写的C语言支持指定压缩算法通用ZIP压缩程序
c语言·开发语言·人工智能·算法·zip·压缩
残醉11 小时前
ChartView的LineSeries 基本介绍与使用
qt