【学写LibreCAD】 2.1 pdf_print_loop文件
pdf_print_loop.h和pdf_print_loop.cpp文件是 LibreCAD 项目中用于将 DXF 文件打印为 PDF 文件的核心模块。它通过 Qt 的 QPrinter 类实现了 PDF 文件的生成,并结合 LibreCAD 的图形处理功能,能够处理单页和多页打印任务。
头文件(pdf_print_loop.h)
头文件包含结构体定义
struct PdfPrintParams {
QStringList dxfFiles;
QString outDir;
QString outFile;
int resolution = 1200;
bool centerOnPage=false;
bool fitToPage=false;
bool monochrome=false;
bool grayscale=false;
double scale = 0.0; // If scale <= 0.0, use value from dxf file.
RS_Vector pageSize; // If zeros, use value from dxf file.
struct {
double left = -1.0;
double top = -1.0;
double right = -1.0;
double bottom = -1.0;
} margins; // If margin < 0.0, use value from dxf file.
int pagesH = 0; // If number of pages < 1,
int pagesV = 0; // use value from dxf file.
};
等价rust代码为:
#[derive(Debug)]
pub struct PdfPrintParams {
pub dxf_files: Vec<String>, // 对应 QStringList dxfFiles
pub out_dir: String, // 对应 QString outDir
pub out_file: String, // 对应 QString outFile
pub resolution: i32, // 对应 int resolution
pub center_on_page: bool, // 对应 bool centerOnPage
pub fit_to_page: bool, // 对应 bool fitToPage
pub monochrome: bool, // 对应 bool monochrome
pub grayscale: bool, // 对应 bool grayscale
pub scale: f64, // 对应 double scale
pub page_size: (Mm, Mm), // 对应 RS_Vector pageSize
pub margins: (Mm, Mm, Mm, Mm), // 边距 (左, 上, 右, 下),对应 margins 结构体
pub pages_h: i32, // 对应 int pagesH
pub pages_v: i32, // 对应 int pagesV
}
impl Default for PdfPrintParams {
fn default() -> Self {
PdfPrintParams {
dxf_files: Vec::new(), // 默认值为空列表
out_dir: String::new(), // 默认值为空字符串
out_file: String::new(), // 默认值为空字符串
resolution: 1200, // 默认值为 1200
center_on_page: false, // 默认值为 false
fit_to_page: false, // 默认值为 false
monochrome: false, // 默认值为 false
grayscale: false, // 默认值为 false
scale: 0.0, // 默认值为 0.0
page_size: (Mm(0.0), Mm(0.0)), // 默认值为 (0.0, 0.0)
margins: (Mm(0.0), Mm(0.0), Mm(0.0), Mm(0.0)), // 默认值为 (0.0, 0.0, 0.0, 0.0)
pages_h: 0, // 默认值为 0
pages_v: 0, // 默认值为 0
}
}
}
程序文件(pdf_print_loop.cpp)分析
源码
#include <QtCore>
#include "rs.h"
#include "rs_graphic.h"
#include "rs_painter.h"
#include "lc_printing.h"
#include "rs_staticgraphicview.h"
#include "rs_units.h"
#include "pdf_print_loop.h"
static bool openDocAndSetGraphic(RS_Document**, RS_Graphic**, const QString&);
static void touchGraphic(RS_Graphic*, PdfPrintParams&);
static void setupPrinterAndPaper(RS_Graphic*, QPrinter&, PdfPrintParams&);
static void drawPage(RS_Graphic*, QPrinter&, RS_Painter&);
void PdfPrintLoop::run()
{
if (params.outFile.isEmpty()) {
for (auto &&f : params.dxfFiles) {
printOneDxfToOnePdf(f);
}
} else {
printManyDxfToOnePdf();
}
emit finished();
}
void PdfPrintLoop::printOneDxfToOnePdf(const QString& dxfFile) {
// Main code logic and flow for this method is originally stolen from
// QC_ApplicationWindow::slotFilePrint(bool printPDF) method.
// But finally it was split in to smaller parts.
QFileInfo dxfFileInfo(dxfFile);
params.outFile =
(params.outDir.isEmpty() ? dxfFileInfo.path() : params.outDir)
+ "/" + dxfFileInfo.completeBaseName() + ".pdf";
RS_Document *doc;
RS_Graphic *graphic;
if (!openDocAndSetGraphic(&doc, &graphic, dxfFile))
return;
qDebug() << "Printing" << dxfFile << "to" << params.outFile << ">>>>";
touchGraphic(graphic, params);
QPrinter printer(QPrinter::HighResolution);
setupPrinterAndPaper(graphic, printer, params);
RS_Painter painter(&printer);
if (params.monochrome)
painter.setDrawingMode(RS2::ModeBW);
drawPage(graphic, printer, painter);
painter.end();
qDebug() << "Printing" << dxfFile << "to" << params.outFile << "DONE";
delete doc;
}
void PdfPrintLoop::printManyDxfToOnePdf() {
struct DxfPage {
RS_Document* doc;
RS_Graphic* graphic;
QString dxfFile;
QPageSize::PageSizeId paperSize;
};
if (!params.outDir.isEmpty()) {
QFileInfo outFileInfo(params.outFile);
params.outFile = params.outDir + "/" + outFileInfo.fileName();
}
QVector<DxfPage> pages;
int nrPages = 0;
// FIXME: Should probably open and print all dxf files in one 'for' loop.
// Tried but failed to do this. It looks like some 'chicken and egg'
// situation for the QPrinter and RS_PainterQt. Therefore, first open
// all dxf files and apply required actions. Then run another 'for'
// loop for actual printing.
for (auto dxfFile : params.dxfFiles) {
DxfPage page;
page.dxfFile = dxfFile;
if (!openDocAndSetGraphic(&page.doc, &page.graphic, dxfFile))
continue;
qDebug() << "Opened" << dxfFile;
touchGraphic(page.graphic, params);
pages.append(page);
nrPages++;
}
QPrinter printer(QPrinter::HighResolution);
if (nrPages > 0) {
// FIXME: Is it possible to set up printer and paper for every
// opened dxf file and tie them with painter? For now just using
// data extracted from the first opened dxf file for all pages.
setupPrinterAndPaper(pages.at(0).graphic, printer, params);
}
RS_Painter painter(&printer);
if (params.monochrome)
painter.setDrawingMode(RS2::ModeBW);
// And now it's time to actually print all previously opened dxf files.
for (auto page : pages) {
nrPages--;
qDebug() << "Printing" << page.dxfFile
<< "to" << params.outFile << ">>>>";
drawPage(page.graphic, printer, painter);
qDebug() << "Printing" << page.dxfFile
<< "to" << params.outFile << "DONE";
delete page.doc;
if (nrPages > 0)
printer.newPage();
}
painter.end();
}
static bool openDocAndSetGraphic(RS_Document** doc, RS_Graphic** graphic,
const QString& dxfFile)
{
*doc = new RS_Graphic();
if (!(*doc)->open(dxfFile, RS2::FormatUnknown)) {
qDebug() << "ERROR: Failed to open document" << dxfFile;
delete *doc;
return false;
}
*graphic = (*doc)->getGraphic();
if (*graphic == nullptr) {
qDebug() << "ERROR: No graphic in" << dxfFile;
delete *doc;
return false;
}
return true;
}
static void touchGraphic(RS_Graphic* graphic, PdfPrintParams& params)
{
graphic->calculateBorders();
graphic->setMargins(params.margins.left, params.margins.top,
params.margins.right, params.margins.bottom);
graphic->setPagesNum(params.pagesH, params.pagesV);
if (params.scale > 0.0)
graphic->setPaperScale(params.scale);
if (params.pageSize != RS_Vector(0.0, 0.0))
graphic->setPaperSize(params.pageSize);
if (params.fitToPage)
graphic->fitToPage(); // fit and center
else if (params.centerOnPage)
graphic->centerToPage();
}
static void setupPrinterAndPaper(RS_Graphic* graphic, QPrinter& printer,
PdfPrintParams& params)
{
bool landscape = false;
RS2::PaperFormat pf = graphic->getPaperFormat(&landscape);
QPageSize::PageSizeId paperSize = LC_Printing::rsToQtPaperFormat(pf);
if (paperSize == QPageSize::Custom){
RS_Vector r = graphic->getPaperSize();
RS_Vector s = RS_Units::convert(r, graphic->getUnit(),
RS2::Millimeter);
if (landscape)
s = s.flipXY();
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
printer.setPageSize(QPageSize{QSizeF{s.x,s.y}, QPageSize::Millimeter});
#else
printer.setPaperSize(QSizeF{s.x,s.y}, QPrinter::Millimeter);
#endif
} else {
printer.setPageSize(paperSize);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
printer.setPageOrientation(landscape ? QPageLayout::Landscape : QPageLayout::Portrait);
#else
printer.setOrientation(landscape ? QPrinter::Landscape : QPrinter::Portrait);
#endif
printer.setOutputFileName(params.outFile);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setResolution(params.resolution);
printer.setFullPage(true);
if (params.grayscale)
printer.setColorMode(QPrinter::GrayScale);
else
printer.setColorMode(QPrinter::Color);
}
static void drawPage(RS_Graphic* graphic, QPrinter& printer,
RS_Painter& painter)
{
double printerFx = (double)printer.width() / printer.widthMM();
double printerFy = (double)printer.height() / printer.heightMM();
double marginLeft = graphic->getMarginLeft();
double marginTop = graphic-> getMarginTop();
double marginRight = graphic->getMarginRight();
double marginBottom = graphic->getMarginBottom();
painter.setClipRect(marginLeft * printerFx, marginTop * printerFy,
printer.width() - (marginLeft + marginRight) * printerFx,
printer.height() - (marginTop + marginBottom) * printerFy);
RS_StaticGraphicView gv(printer.width(), printer.height(), &painter);
gv.setPrinting(true);
gv.setBorders(0,0,0,0);
gv.updateSettings(graphic);
double fx = printerFx * RS_Units::getFactorToMM(graphic->getUnit());
double fy = printerFy * RS_Units::getFactorToMM(graphic->getUnit());
double f = (fx + fy) / 2.0;
double scale = graphic->getPaperScale();
gv.setOffset((int)(graphic->getPaperInsertionBase().x * f),
(int)(graphic->getPaperInsertionBase().y * f));
gv.setFactor(f*scale);
gv.setContainer(graphic);
double baseX = graphic->getPaperInsertionBase().x;
double baseY = graphic->getPaperInsertionBase().y;
int numX = graphic->getPagesNumHoriz();
int numY = graphic->getPagesNumVert();
RS_Vector printArea = graphic->getPrintAreaSize(false);
for (int pY = 0; pY < numY; pY++) {
double offsetY = printArea.y * pY;
for (int pX = 0; pX < numX; pX++) {
double offsetX = printArea.x * pX;
// First page is created automatically.
// Extra pages must be created manually.
if (pX > 0 || pY > 0) printer.newPage();
gv.setOffset((int)((baseX - offsetX) * f),
(int)((baseY - offsetY) * f));
gv.drawEntity(&painter, graphic );
}
}
}
源码分析
- 文件头信息
- 代码中包含了多个 LibreCAD 的核心头文件,如 rs.h、rs_graphic.h 等,这些文件定义了与图形处理相关的类和函数。
- PdfPrintLoop 类
-
PdfPrintLoop 类负责处理 DXF 文件的打印任务。它有两个主要的公共方法:
-
printOneDxfToOnePdf:将一个 DXF 文件打印为一个 PDF 文件。
-
printManyDxfToOnePdf:将多个 DXF 文件打印到一个 PDF 文件中,每个 DXF 文件对应 PDF 文件中的一页。
-
- printOneDxfToOnePdf 方法
-
该方法将一个 DXF 文件打印为一个 PDF 文件。主要步骤如下:
-
设置输出文件路径:根据输入的 DXF 文件路径和输出目录,生成 PDF 文件的输出路径。
-
打开 DXF 文件并获取图形对象:通过 openDocAndSetGraphic 函数打开 DXF 文件,并获取其中的图形对象 RS_Graphic。
-
调整图形设置:调用 touchGraphic 函数,根据打印参数(如边距、页面数量、缩放比例等)调整图形对象的设置。
-
设置打印机和纸张:通过 setupPrinterAndPaper 函数配置 QPrinter 对象,设置纸张大小、方向、分辨率等。
-
绘制页面:使用 RS_Painter 对象将图形内容绘制到 PDF 文件中。
-
清理资源:打印完成后,删除文档对象以释放资源。
-
- printManyDxfToOnePdf 方法
-
该方法将多个 DXF 文件打印到一个 PDF 文件中,每个 DXF 文件对应 PDF 文件中的一页。主要步骤如下:
-
设置输出文件路径:根据输入的 DXF 文件列表和输出目录,生成 PDF 文件的输出路径。
-
打开所有 DXF 文件并获取图形对象:通过 openDocAndSetGraphic 函数打开每个 DXF 文件,并获取其中的图形对象 RS_Graphic。
-
调整图形设置:对每个图形对象调用 touchGraphic 函数,根据打印参数调整图形设置。
-
设置打印机和纸张:使用第一个 DXF 文件的图形对象来配置 QPrinter 对象,设置纸张大小、方向、分辨率等。
-
绘制所有页面:使用 RS_Painter 对象将每个 DXF 文件的图形内容绘制到 PDF 文件中,每个 DXF 文件对应一页。
-
清理资源:打印完成后,删除所有文档对象以释放资源。
-
- 辅助函数
-
openDocAndSetGraphic:打开 DXF 文件并获取图形对象。如果文件打开失败或文件中没有图形对象,则返回 false。
-
touchGraphic:根据打印参数调整图形对象的设置,如边距、页面数量、缩放比例等。
-
setupPrinterAndPaper:根据图形对象的纸张大小和方向配置 QPrinter 对象。
-
drawPage:将图形对象的内容绘制到 PDF 文件中,处理多页打印的情况。
- Qt 和 LibreCAD 的集成
-
代码中使用了 Qt 的 QPrinter 类来处理 PDF 文件的生成,QPrinter 提供了丰富的打印功能,如设置纸张大小、方向、分辨率等。
-
RS_Painter 是 LibreCAD 中的一个绘图类,负责将图形内容绘制到 QPrinter 上。
-
RS_Graphic 是 LibreCAD 中的图形对象,包含了 DXF 文件中的所有图形数据。
-
多页打印处理
在 drawPage 函数中,处理了多页打印的情况。如果图形对象设置了多个页面(水平和垂直方向),则会自动生成多个 PDF 页面,并将图形内容分别绘制到每个页面上。 -
调试信息
代码中使用了 qDebug() 输出调试信息,方便开发者跟踪打印过程中的各个步骤。 -
Qt 版本兼容性
代码中考虑了不同 Qt 版本的兼容性,特别是在设置纸张大小和方向时,使用了条件编译来处理不同版本的 Qt API。
等价rust代码
Rust 中没有直接对应的 Qt 库,但可以使用 printpdf库来生成 PDF 文件,dxf 库解析 DXF 文件。绘制图形的工作初步考虑lyon。
本文件调用RS_Document等类,我们先实现底层内容,该文件等价的rust代码在后期完善。