【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();
    }
}
相关推荐
用户805533698033 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner3 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz8 天前
QML Hello World 入门示例
qt
xcyxiner11 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner12 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner12 天前
DicomViewer (添加模型类)3
qt
xcyxiner13 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00614 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术14 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript