功能:在界面依次打印QPainterPath中的元素。

代码作者:Qwen3-Coder大模型。

#ifndef VIRTUALPRINTERWIDGET_H
#define VIRTUALPRINTERWIDGET_H

#include <QWidget>
#include <QPainterPath>
#include <QTimer>

class VirtualPrinterWidget : public QWidget {
    Q_OBJECT

public:
    explicit VirtualPrinterWidget(QWidget *parent = nullptr);

    void setPath(const QPainterPath &path);
    void startPrinting();
    void stopPrinting();
    void resetPrinter();
    bool isPrintingActive() const;

protected:
    void paintEvent(QPaintEvent *event) override;

private slots:
    void onTimerTimeout();

private:
    QPointF cubicBezierPoint(const QPointF &p0, const QPointF &c1, const QPointF &c2, const QPointF &p1, qreal t);
    void updateCurrentPartialElementPath();

    QPainterPath fullPath;
    QPainterPath partialPath;
    QPainterPath currentPartialElementPath;

    QTimer *timer;
    int currentPathIndex;
    qreal currentSubPathProgress;
    bool isPrinting;
};

#endif // VIRTUALPRINTERWIDGET_H
#include "VirtualPrinterWidget.h"
#include <QPainter>
#include <QTimer>
#include <QDebug>
#include <QPen>
#include <QFont>

VirtualPrinterWidget::VirtualPrinterWidget(QWidget *parent)
    : QWidget(parent) {
    currentPathIndex = 0;
    currentSubPathProgress = 0.0;
    isPrinting = false;
    timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this, &VirtualPrinterWidget::onTimerTimeout);

    setWindowTitle("Virtual Printer");
    resize(800, 600);
}

void VirtualPrinterWidget::setPath(const QPainterPath &path) {
    fullPath = path;
    resetPrinter();
    update();
}

void VirtualPrinterWidget::startPrinting() {
    if (isPrinting) return;

    isPrinting = true;
    timer->start(10);
}

void VirtualPrinterWidget::stopPrinting() {
    timer->stop();
    isPrinting = false;
}

void VirtualPrinterWidget::resetPrinter()
{
    stopPrinting();
    currentPathIndex = 0;
    currentSubPathProgress = 0.0;
    partialPath = QPainterPath();
    currentPartialElementPath = QPainterPath(); // 重置当前元素路径
}

bool VirtualPrinterWidget::isPrintingActive() const {
    return isPrinting;
}

void VirtualPrinterWidget::paintEvent(QPaintEvent *event) {
    Q_UNUSED(event);
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    painter.fillRect(rect(), Qt::white);

    // 绘制已完成的部分 (蓝色)
    QPen pen(Qt::blue, 2);
    painter.setPen(pen);
    painter.drawPath(partialPath);

    // 绘制当前正在绘制的部分 (红色)
    // 只有当 progress < 1.0 时才绘制,避免在完成瞬间重复绘制
    if (currentPathIndex < fullPath.elementCount() && isPrinting && currentSubPathProgress < 1.0) {
        QPen currentPen(Qt::red, 2);
        painter.setPen(currentPen);
        painter.drawPath(currentPartialElementPath);
    }

    // 绘制状态信息
    painter.setPen(Qt::black);
    painter.drawText(10, height() - 10, QString("Element: %1/%2, Progress: %3").arg(currentPathIndex).arg(fullPath.elementCount()).arg(currentSubPathProgress, 0, 'f', 2));
}

void VirtualPrinterWidget::onTimerTimeout() {
    if (!isPrinting) {
        timer->stop();
        return;
    }

    if (currentPathIndex >= fullPath.elementCount()) {
        stopPrinting();
        return;
    }

    // 计算当前元素的进度
    currentSubPathProgress += 0.05;
    if (currentSubPathProgress >= 1.0) {
        currentSubPathProgress = 1.0; // 确保精确为1.0
    }

    // 更新当前元素的绘制路径
    updateCurrentPartialElementPath();

    if (currentSubPathProgress >= 1.0) {
        // 进度完成,将当前元素的完整路径合并到已完成部分
        // 此时 currentPartialElementPath 包含完整元素
        partialPath.addPath(currentPartialElementPath);

        // 重置当前元素路径
        currentPartialElementPath = QPainterPath();

        // 移动到下一个元素
        auto element = fullPath.elementAt(currentPathIndex);
        if (element.type == QPainterPath::MoveToElement) {
            currentPathIndex++;
        } else if (element.type == QPainterPath::LineToElement) {
            currentPathIndex++;
        } else if (element.type == QPainterPath::CurveToElement) {
            // 贝塞尔曲线需要跳过两个点 (C2, P1)
            if (currentPathIndex + 2 < fullPath.elementCount()) {
                currentPathIndex += 3; // 跳过 CurveTo, CurveTo, LineTo (或MoveTo)
            } else {
                // 格式错误,跳过当前元素并警告
                qDebug() << "Warning: Malformed curve, skipping.";
                currentPathIndex++;
            }
        }

        // 重置进度,准备绘制下一个元素
        currentSubPathProgress = 0.0;
    }

    update(); // 请求重绘
}

void VirtualPrinterWidget::updateCurrentPartialElementPath() {
    // 清空当前元素路径,重新计算
    currentPartialElementPath = QPainterPath();

    if (currentPathIndex >= fullPath.elementCount()) {
        return;
    }

    auto element = fullPath.elementAt(currentPathIndex);

    if (element.type == QPainterPath::MoveToElement) {
        // MoveTo 本身不绘制,但它定义了下一个 LineTo 或 CurveTo 的起点
        // 如果下一个元素是 LineTo 或 CurveTo,则开始绘制那部分
        if (currentPathIndex + 1 < fullPath.elementCount()) {
            auto nextElement = fullPath.elementAt(currentPathIndex + 1);
            if (nextElement.type == QPainterPath::LineToElement) {
                // 绘制从 MoveTo 到 LineTo 的部分
                QPointF p1 = QPointF(element.x, element.y);
                QPointF p2 = QPointF(nextElement.x, nextElement.y);
                QPointF currentPos = p1 + (p2 - p1) * currentSubPathProgress;
                currentPartialElementPath.moveTo(p1);
                currentPartialElementPath.lineTo(currentPos);
            } else if (nextElement.type == QPainterPath::CurveToElement && currentPathIndex + 2 < fullPath.elementCount()) {
                // 处理 MoveTo -> CurveTo -> CurveTo -> (LineTo/MoveTo)
                auto ctrlPt1 = QPointF(nextElement.x, nextElement.y);
                auto ctrlPt2 = QPointF(fullPath.elementAt(currentPathIndex + 2).x, fullPath.elementAt(currentPathIndex + 2).y);
                auto endPt = QPointF(fullPath.elementAt(currentPathIndex + 3).x, fullPath.elementAt(currentPathIndex + 3).y);
                QPointF p1 = QPointF(element.x, element.y);
                QPointF currentPos = cubicBezierPoint(p1, ctrlPt1, ctrlPt2, endPt, currentSubPathProgress);
                currentPartialElementPath.moveTo(p1);
                currentPartialElementPath.lineTo(currentPos); // 简化显示,用直线段模拟
            }
        }
    } else if (element.type == QPainterPath::LineToElement) {
        // 找到上一个点 (MoveTo 或 LineTo 或 CurveTo的终点)
        // 简化:假设起点是 partialPath 的最后一个点或全局起点
        QPointF startPoint;
        if (partialPath.elementCount() > 0) {
            auto lastElement = partialPath.elementAt(partialPath.elementCount() - 1);
            startPoint = QPointF(lastElement.x, lastElement.y);
        } else {
            // 如果 partialPath 为空,需要找到当前 LineTo 的逻辑起点
            // 通常在 LineTo 之前会有一个 MoveTo
            int prevIndex = currentPathIndex - 1;
            while (prevIndex >= 0) {
                if (fullPath.elementAt(prevIndex).type == QPainterPath::MoveToElement) {
                    startPoint = QPointF(fullPath.elementAt(prevIndex).x, fullPath.elementAt(prevIndex).y);
                    break;
                }
                prevIndex--;
            }
            // 如果找不到 MoveTo,则起点可能是 (0,0) 或上一个 LineTo,这里简化处理
        }

        QPointF p1 = startPoint;
        QPointF p2 = QPointF(element.x, element.y);
        QPointF currentPos = p1 + (p2 - p1) * currentSubPathProgress;
        currentPartialElementPath.moveTo(p1);
        currentPartialElementPath.lineTo(currentPos);
    } else if (element.type == QPainterPath::CurveToElement) {
        // 处理三次贝塞尔曲线 (MoveTo P0, CurveTo C1, CurveTo C2, LineTo/MoveTo P1)
        int curveStartIndex = currentPathIndex - 1; // 理论上是 MoveTo
        if (curveStartIndex >= 0 && fullPath.elementAt(curveStartIndex).type == QPainterPath::MoveToElement &&
            currentPathIndex + 2 < fullPath.elementCount() &&
            fullPath.elementAt(currentPathIndex + 1).type == QPainterPath::CurveToElement && // C2
            (fullPath.elementAt(currentPathIndex + 2).type == QPainterPath::LineToElement || fullPath.elementAt(currentPathIndex + 2).type == QPainterPath::MoveToElement) // P1
            ) {
            QPointF p0(fullPath.elementAt(curveStartIndex).x, fullPath.elementAt(curveStartIndex).y); // 起点
            QPointF c1(fullPath.elementAt(currentPathIndex).x, fullPath.elementAt(currentPathIndex).y); // 控制点1
            QPointF c2(fullPath.elementAt(currentPathIndex + 1).x, fullPath.elementAt(currentPathIndex + 1).y); // 控制点2
            QPointF p1(fullPath.elementAt(currentPathIndex + 2).x, fullPath.elementAt(currentPathIndex + 2).y); // 终点

            QPointF currentPos = cubicBezierPoint(p0, c1, c2, p1, currentSubPathProgress);

            currentPartialElementPath.moveTo(p0);
            currentPartialElementPath.lineTo(currentPos); // 简化显示
        } else {
            qDebug() << "Warning: Malformed curve in path during update, skipping.";
            // 如果格式不匹配,currentPartialElementPath 保持为空
        }
    }
}

QPointF VirtualPrinterWidget::cubicBezierPoint(const QPointF &p0, const QPointF &c1, const QPointF &c2, const QPointF &p1, qreal t) {
    qreal u = 1 - t;
    qreal tt = t * t;
    qreal uu = u * u;
    qreal uuu = uu * u;
    qreal ttt = tt * t;

    QPointF p = uuu * p0;
    p += 3 * uu * t * c1;
    p += 3 * u * tt * c2;
    p += ttt * p1;
    return p;
}
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐