QPdfWriter生成pdf自动生成页眉页脚及水印

最近开发的一个项目要求通过软件生成pdf报告,并要求为每页添加页眉页脚及水印。 pdf内容可以使用QTextDocument,QTextDocument支持文档结构,同时支持富文本。使用文档结构和富文本完全可以满足段落文本、图片、表格排版。最后通过QTextDocument转为pdf。
实现的效果:
在这里插入图片描述

一、遇到的问题

QPdfWriter本身是不支持设置页眉页脚和水印的,需要通过QPainter绘制。网上提供的方法大都存在下述问题:
绘制页眉页脚、水印,无法自动换页,需要通过 n e w P a g e ( ) ,然后继续绘制 \color{#FF0000}{绘制页眉页脚、水印,无法自动换页,需要通过newPage(),然后继续绘制} 绘制页眉页脚、水印,无法自动换页,需要通过newPage(),然后继续绘制
二、解决思路

    QPdfWriter pdfWriter(fileName);
    QTextDocument textDocument;
    // 通过QTextDocument对pdf文档进行排版 
    textDocument.print(&pdfWriter); // 将textDocument内容输出到pdfWriter,生成pdf完成

QTextDocument 通过pint方法将内容输出到QPdfWriter,那么pint方法内部是如何将内容绘制到QPdfWriter的呢?接下来我们通过源码看看内部是如何实现的。

void QTextDocument::print(QPagedPaintDevice *printer) const
{
    Q_D(const QTextDocument);

    if (!printer)
        return;

    bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull()
                             && d->pageSize.height() != INT_MAX;

    QPagedPaintDevicePrivate *pd = QPagedPaintDevicePrivate::get(printer);

    // ### set page size to paginated size?
    QMarginsF m = printer->pageLayout().margins(QPageLayout::Millimeter);
    if (!documentPaginated && m.left() == 0 && m.right() == 0 && m.top() == 0 && m.bottom() == 0) {
        m.setLeft(2.);
        m.setRight(2.);
        m.setTop(2.);
        m.setBottom(2.);
        printer->setPageMargins(m, QPageLayout::Millimeter);  // 设置页面边距
    }
    // ### use the margins correctly
    QPainter p(printer);  // 通过QPdfWriter构造QPainter

    // Check that there is a valid device to print to.
    if (!p.isActive())
        return;

    const QTextDocument *doc = this;
    QScopedPointer<QTextDocument> clonedDoc;
    (void)doc->documentLayout(); // make sure that there is a layout

    QRectF body = QRectF(QPointF(0, 0), d->pageSize);
    QPointF pageNumberPos;
    
    // 获取DPI,DPI是什么具体我也不清楚
    qreal sourceDpiX = qt_defaultDpiX();
    qreal sourceDpiY = qt_defaultDpiY();
    const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
    const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;

	// 通过断点调试documentPaginated的值为false,我们直接看else的逻辑
    if (documentPaginated) {

        QPaintDevice *dev = doc->documentLayout()->paintDevice();
        if (dev) {
            sourceDpiX = dev->logicalDpiX();
            sourceDpiY = dev->logicalDpiY();
        }
        // scale to dpi
        p.scale(dpiScaleX, dpiScaleY);

        QSizeF scaledPageSize = d->pageSize;
        scaledPageSize.rwidth() *= dpiScaleX;
        scaledPageSize.rheight() *= dpiScaleY;

        const QSizeF printerPageSize(printer->width(), printer->height());

        // scale to page
        p.scale(printerPageSize.width() / scaledPageSize.width(),
                printerPageSize.height() / scaledPageSize.height());
    } else {
        doc = clone(const_cast<QTextDocument *>(this));
        clonedDoc.reset(const_cast<QTextDocument *>(doc));

        for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock();
             srcBlock.isValid() && dstBlock.isValid();
             srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) {
            dstBlock.layout()->setFormats(srcBlock.layout()->formats());
        }

        QAbstractTextDocumentLayout *layout = doc->documentLayout();
        layout->setPaintDevice(p.device());

        // copy the custom object handlers
        layout->d_func()->handlers = documentLayout()->d_func()->handlers;

        // 2 cm margins, scaled to device in QTextDocumentLayoutPrivate::layoutFrame
        const int horizontalMargin = int((2/2.54)*sourceDpiX);
        const int verticalMargin = int((2/2.54)*sourceDpiY);
        QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
        fmt.setLeftMargin(horizontalMargin);
        fmt.setRightMargin(horizontalMargin);
        fmt.setTopMargin(verticalMargin);
        fmt.setBottomMargin(verticalMargin);
        doc->rootFrame()->setFrameFormat(fmt);
        // pageNumberPos must be in device coordinates, so scale to device here
        const int dpiy = p.device()->logicalDpiY();
        body = QRectF(0, 0, printer->width(), printer->height()); // body很重要,是页面内容区域的大小
        pageNumberPos = QPointF(body.width() - horizontalMargin * dpiScaleX,
                                body.height() - verticalMargin * dpiScaleY
                                + QFontMetrics(doc->defaultFont(), p.device()).ascent()
                                + 5 * dpiy / 72.0); // pageNumberPos: 绘制页码的坐标
    // fromPage和toPage可以理解为从第几页到第几页
    int fromPage = pd->fromPage;
    int toPage = pd->toPage;

    if (fromPage == 0 && toPage == 0) {
        fromPage = 1;
        toPage = doc->pageCount();
    }
    // paranoia check
    fromPage = qMax(1, fromPage);
    toPage = qMin(doc->pageCount(), toPage);

    if (toPage < fromPage) {
        // if the user entered a page range outside the actual number
        // of printable pages, just return
        return;
    }
    int page = fromPage;
    // 通过while循环绘制完所有页
    while (true) {
        printPage(page, &p, doc, body, pageNumberPos); // 绘制单独的一整页,各个参数前面已经介绍到了

        if (page == toPage)
            break;
        ++page;
        if (!printer->newPage()) // 绘制完一页后,通过newPage创建一个新页
            return;
    }
}   

通过对QTextDocument::print方法的分析,发现最后使用printPage完成pdf的整个绘制,那么我们岂不是重写print方法或printPage方法就可以完成页面内容、页眉页脚、水印的绘制了。但又发现print方法或printPage方法不是虚方法无法重写。

QTextDocument内部其实已经自动完成了分页,通过pageCount方法就可以获取到总页数,页码坐标和页面内容区域源码也提供了计算方法。既然如此,那我们可以自己实现print和printPage方法来完成绘制页面内容、页眉页脚、水印的功能。

三、解决方法

先把print和printPage整个源码搬过来,然后删掉和自己无关的,再添加上所要实现的功能代码
print方法的实现:

void print(QTextDocument* doc, QPdfWriter* printer)
{
    printer->setPageMargins(QMarginsF(20, 20, 20, 20), QPageLayout::Millimeter);  // 页面边距设置

    QPainter p(printer);

    (void)doc->documentLayout(); // make sure that there is a layout

    QRectF body = QRectF(QPointF(0, 0), doc->pageSize());  // 页面内容区域矩形大小
    QPointF pageNumberPos;

	// 通过源码断点看到DPI的默认值,此处先写死
    qreal sourceDpiX = 2.0833333333333335;
    qreal sourceDpiY = 2.0833333333333335;
    const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX;
    const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY;

    QAbstractTextDocumentLayout* layout = doc->documentLayout();
    layout->setPaintDevice(p.device());

    const int horizontalMargin = int((2 / 2.54) * sourceDpiX);
    const int verticalMargin = int((2 / 2.54) * sourceDpiY);

    const int dpiy = p.device()->logicalDpiY();
    body = QRectF(0, 0, printer->width(), printer->height());
    pageNumberPos = QPointF(body.width() - horizontalMargin * dpiScaleX,
                            body.height() - verticalMargin * dpiScaleY
                            + QFontMetrics(doc->defaultFont(), p.device()).ascent()
                            + 5 * dpiy / 72.0);
    doc->setPageSize(body.size());
    
    int fromPage = 1;  // 开始页面直接给定为1,我希望从第一页开始绘制
    int toPage = doc->pageCount(); // 结束页面为页面总数

    if (fromPage == 0 && toPage == 0)
    {
        fromPage = 1;
        toPage = doc->pageCount();
    }
    // paranoia check
    fromPage = qMax(1, fromPage);
    toPage = qMin(doc->pageCount(), toPage);

    int page = fromPage;
    while (true)
    {
        printPage(page, &p, doc, body, pageNumberPos);

        if (page == toPage)
            break;
        ++page;
        if (!printer->newPage())
            return;
    }
}

printPage方法的实现:

static void printPage(int index, QPainter* painter, const QTextDocument* doc, const QRectF &body, const QPointF &pageNumberPos)
{
    QRectF view(0, (index - 1) * body.height(), body.width(), body.height());

    painter->save();
    painter->translate(-(view.topLeft() + QPoint(0, 70)));
    QPen pen1;
    pen1.setColor(Qt::black);
    pen1.setWidth(5);
    painter->setPen(pen1);
    // 页眉
    QRect pageTopRect(0, view.y(), view.width(), 50);
    painter->drawText(pageTopRect, Qt::AlignVCenter | Qt::AlignRight, "报告编号:20240315155503");
    pageTopRect.setWidth(300);
    painter->drawPixmap(pageTopRect, QPixmap(":/Snipaste_2024-04-21_18-54-22.png"));
    painter->drawLine(0, view.y() + 52, view.width(), view.y() + 52);
    // 页脚
    QRect pageBottomRect(0, view.y() + view.height() + 70, view.width(), 50);
    auto avgWidth = pageBottomRect.width() / 3;
    auto leftBottomRect = pageBottomRect;
    leftBottomRect.setWidth(avgWidth);
    auto centerBottomRect = pageBottomRect;
    centerBottomRect.setX(avgWidth);
    centerBottomRect.setWidth(avgWidth);
    auto rightBottomRect = pageBottomRect;
    auto rightBottomRect = pageBottomRect;
    rightBottomRect.setX(avgWidth * 2);
    rightBottomRect.setWidth(avgWidth);
    painter->drawText(leftBottomRect, Qt::AlignVCenter | Qt::AlignLeft, "检测日期:2024/3/15");
    painter->drawText(centerBottomRect, Qt::AlignCenter, "检测人员:");
    painter->drawText(rightBottomRect, Qt::AlignVCenter | Qt::AlignRight, "审核人员");
    painter->restore();

    painter->save();
    // 绘制水印
    painter->translate(-view.topLeft());
    QPen pen;
    pen.setColor(Qt::red);
    pen.setWidth(10);
    painter->setPen(pen);
    painter->translate(view.center());
    painter->rotate(-30);
    painter->translate(-view.center());
    for (int x = view.x() - 1000; x < view.width() + 1000; x += 500)
    {
        for (int y = view.y() - 1000; y < view.y() + view.height() + 1000; y += 300)
        {
            painter->drawText(x, y, "2024/04/21");
        }
    }
    painter->restore();

    painter->save();
    painter->translate(body.left(), body.top() - (index - 1) * body.height());
    
    QAbstractTextDocumentLayout* layout = doc->documentLayout();
    QAbstractTextDocumentLayout::PaintContext ctx;

    painter->setClipRect(view);
    ctx.clip = view;

    // don't use the system palette text as default text color, on HP/UX
    // for example that's white, and white text on white paper doesn't
    // look that nice
    ctx.palette.setColor(QPalette::Text, Qt::black);

    layout->draw(painter, ctx);

    if (!pageNumberPos.isNull())
    {
        painter->setClipping(false);
        painter->setFont(QFont(doc->defaultFont()));
        const QString pageString = QString::number(index);
        // 绘制页码
        painter->drawText(view.width() / 2, view.y() + view.height() + 70, pageString);
    }

    painter->restore();
}

主方法修改:

    QPdfWriter pdfWriter(fileName);
    QTextDocument textDocument;
    
    // 通过QTextDocument对pdf文档进行排版 
    
    //textDocument.print(&pdfWriter); // 将textDocument内容输出到pdfWriter,生成pdf完成
    print(&textDocument,&pdfWriter); // 使用新方法代替Qt提供的方法
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值