前言
? ? ? ? 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打印機 |
|
|
網絡IPP打印機 |
|
|
Windows共享打印機 |
|
|
LPD協議打印機 |
|
|
AppSocket打印機 |
|
|
以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
??函數?? | ??描述?? |
---|---|
| 獲取打印機列表 |
| 提交文件打印任務 |
| 獲取默認打印機名稱 |
| 獲取最后一次錯誤的描述 |
| 添加打印選項 |
| 獲取打印機的 PPD 文件路徑 |
| 創建臨時文件 |
| 驗證打印選項是否有效 |
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()); // 顯式轉換為QSizepainter.drawImage(printer.pageRect(QPrinter::Point), image);}painter.end();}
}