【学写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)

头文件包含结构体定义

cpp 复制代码
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代码为:

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)分析

源码
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 );
        }
    }
}
源码分析
  1. 文件头信息
  • 代码中包含了多个 LibreCAD 的核心头文件,如 rs.h、rs_graphic.h 等,这些文件定义了与图形处理相关的类和函数。
  1. PdfPrintLoop 类
  • PdfPrintLoop 类负责处理 DXF 文件的打印任务。它有两个主要的公共方法:

    • printOneDxfToOnePdf:将一个 DXF 文件打印为一个 PDF 文件。

    • printManyDxfToOnePdf:将多个 DXF 文件打印到一个 PDF 文件中,每个 DXF 文件对应 PDF 文件中的一页。

  1. printOneDxfToOnePdf 方法
  • 该方法将一个 DXF 文件打印为一个 PDF 文件。主要步骤如下:

    1. 设置输出文件路径:根据输入的 DXF 文件路径和输出目录,生成 PDF 文件的输出路径。

    2. 打开 DXF 文件并获取图形对象:通过 openDocAndSetGraphic 函数打开 DXF 文件,并获取其中的图形对象 RS_Graphic。

    3. 调整图形设置:调用 touchGraphic 函数,根据打印参数(如边距、页面数量、缩放比例等)调整图形对象的设置。

    4. 设置打印机和纸张:通过 setupPrinterAndPaper 函数配置 QPrinter 对象,设置纸张大小、方向、分辨率等。

    5. 绘制页面:使用 RS_Painter 对象将图形内容绘制到 PDF 文件中。

    6. 清理资源:打印完成后,删除文档对象以释放资源。

  1. printManyDxfToOnePdf 方法
  • 该方法将多个 DXF 文件打印到一个 PDF 文件中,每个 DXF 文件对应 PDF 文件中的一页。主要步骤如下:

    1. 设置输出文件路径:根据输入的 DXF 文件列表和输出目录,生成 PDF 文件的输出路径。

    2. 打开所有 DXF 文件并获取图形对象:通过 openDocAndSetGraphic 函数打开每个 DXF 文件,并获取其中的图形对象 RS_Graphic。

    3. 调整图形设置:对每个图形对象调用 touchGraphic 函数,根据打印参数调整图形设置。

    4. 设置打印机和纸张:使用第一个 DXF 文件的图形对象来配置 QPrinter 对象,设置纸张大小、方向、分辨率等。

    5. 绘制所有页面:使用 RS_Painter 对象将每个 DXF 文件的图形内容绘制到 PDF 文件中,每个 DXF 文件对应一页。

    6. 清理资源:打印完成后,删除所有文档对象以释放资源。

  1. 辅助函数
  • openDocAndSetGraphic:打开 DXF 文件并获取图形对象。如果文件打开失败或文件中没有图形对象,则返回 false。

  • touchGraphic:根据打印参数调整图形对象的设置,如边距、页面数量、缩放比例等。

  • setupPrinterAndPaper:根据图形对象的纸张大小和方向配置 QPrinter 对象。

  • drawPage:将图形对象的内容绘制到 PDF 文件中,处理多页打印的情况。

  1. Qt 和 LibreCAD 的集成
  • 代码中使用了 Qt 的 QPrinter 类来处理 PDF 文件的生成,QPrinter 提供了丰富的打印功能,如设置纸张大小、方向、分辨率等。

  • RS_Painter 是 LibreCAD 中的一个绘图类,负责将图形内容绘制到 QPrinter 上。

  • RS_Graphic 是 LibreCAD 中的图形对象,包含了 DXF 文件中的所有图形数据。

  1. 多页打印处理

    在 drawPage 函数中,处理了多页打印的情况。如果图形对象设置了多个页面(水平和垂直方向),则会自动生成多个 PDF 页面,并将图形内容分别绘制到每个页面上。

  2. 调试信息

    代码中使用了 qDebug() 输出调试信息,方便开发者跟踪打印过程中的各个步骤。

  3. Qt 版本兼容性

    代码中考虑了不同 Qt 版本的兼容性,特别是在设置纸张大小和方向时,使用了条件编译来处理不同版本的 Qt API。

等价rust代码

Rust 中没有直接对应的 Qt 库,但可以使用 printpdf库来生成 PDF 文件,dxf 库解析 DXF 文件。绘制图形的工作初步考虑lyon。

本文件调用RS_Document等类,我们先实现底层内容,该文件等价的rust代码在后期完善。

相关推荐
zzh9407738 分钟前
2026年AI文件上传功能实战:聚合站处理图片、PDF、PPT全指南
人工智能·pdf·powerpoint
42tr_k12 小时前
Rust LanceDB 内存不足问题
rust
mengzhi啊15 小时前
Qt Designer UI 界面 拖的两个 QLineEdit,想按 Tab 从第一个跳到第二个
qt
笨笨马甲17 小时前
Qt MQTT
开发语言·qt
Source.Liu17 小时前
【Iced】benches 文件夹分析笔记
rust·iced
鹏大师运维20 小时前
统信UOS上使用WPS PDF独立版
linux·运维·windows·pdf·wps·统信uos·wine
ttod_qzstudio20 小时前
PDF 生成与本地文件操作:浏览器原生文件系统 API 实战
pdf
姓刘的哦20 小时前
Qt实现蚂蚁线
开发语言·qt
Ivy_belief21 小时前
Qt网络编程实战:从零掌握 QUdpSocket 及 UDP 通信
网络·qt·udp
Source.Liu21 小时前
【glam】线性代数库 lib.rs 文件解析
rust·glam