Add PDF page import to the scripting API

qet.addPdfPage() renders one page of a PDF file to an image and
places it, through the same QPdfDocument::render() call, white-
background compositing (a transparent page would otherwise show
whatever is under it, unlike every other placed image) and
DiagramImageItem/AddGraphicsObjectCommand underneath as the "add PDF"
toolbar action's own file/page-selection dialogs.

Only reachable in a build with the QtPdf module (Qt >= 6.4) -- some
Qt6 distributions omit it entirely (see diagrameventaddpdf.h). The
method is still always declared and compiled, guarded internally
instead of with the class itself: a script asking whether qet.addPdfPage
exists must never get "not a function" for a reason it has no way to
discover. Refuses with a clear reason when the module is missing, the
page number is out of range, the file cannot be loaded, or the
resulting render is degenerate.

Verified against a real 2-page PDF (this session installed qt6-pdf-dev,
which was missing here) that the two rendered pages differ in content
and that dpi scales the rendered pixel size linearly, not just that the
call returns something.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-22 15:32:14 +12:00
parent 687837d7a3
commit 9cee71bde2
2 changed files with 98 additions and 0 deletions
+96
View File
@@ -39,6 +39,16 @@
#include "../qetgraphicsitem/conductor.h"
#include "../conductorsegment.h"
#include "../qetgraphicsitem/diagramimageitem.h"
// See diagrameventaddpdf.h: a missing QtPdf module (or Qt < 6.4) is not
// fatal at build time, so addPdfPage() is always declared -- a script
// asking qet.addPdfPage exists must never get "not a function" for a
// reason it has no way to discover -- and logs a clear refusal instead of
// failing to compile.
#ifdef QET_HAS_QTPDF
#include <QPdfDocument>
#include <QPainter>
#endif
#include "../qetgraphicsitem/dynamicelementtextitem.h"
#include "../qetgraphicsitem/independenttextitem.h"
#include "../qetgraphicsitem/qetshapeitem.h"
@@ -2896,6 +2906,92 @@ bool QetScriptApi::deleteImage(int folioIndex, int imageIndex)
return true;
}
/**
@brief QetScriptApi::addPdfPage
Render one page of a PDF file to an image and place it, as the "add
PDF" toolbar action does after its file and page-selection dialogs --
same QPdfDocument::render() call, same white-background compositing
for a transparent PDF, same DiagramImageItem/AddGraphicsObjectCommand
underneath addImage() itself. Only reachable in a build with the
QtPdf module (Qt >= 6.4); refused with a clear reason otherwise, since
a missing module or a too-old Qt is a real possibility this project
ships around (see diagrameventaddpdf.h) rather than something a script
should read as "no such method".
@param pageNumber 1-based, as PdfPagesDialog shows it
@param dpi resolution to render at; the GUI dialog defaults to 150
@return the new image's index in images(), or -1
*/
int QetScriptApi::addPdfPage(int folioIndex, const QString &pdfPath, int pageNumber,
int dpi, double x, double y)
{
#ifndef QET_HAS_QTPDF
Q_UNUSED(folioIndex) Q_UNUSED(pdfPath) Q_UNUSED(pageNumber)
Q_UNUSED(dpi) Q_UNUSED(x) Q_UNUSED(y)
log(QStringLiteral("qet.addPdfPage: this build has no QtPdf module (or Qt < 6.4); "
"PDF page import is unavailable"));
return -1;
#else
if (!m_project) return -1;
if (m_project->isReadOnly()) {
log(QStringLiteral("qet.addPdfPage: project is read-only"));
return -1;
}
const QList<Diagram *> diagrams = m_project->diagrams();
if (folioIndex < 0 || folioIndex >= diagrams.count()) return -1;
if (dpi <= 0) {
log(QStringLiteral("qet.addPdfPage: dpi must be positive, got %1").arg(dpi));
return -1;
}
const QFileInfo info(pdfPath);
if (!info.isFile()) {
log(QStringLiteral("qet.addPdfPage: '%1' is not a file").arg(pdfPath));
return -1;
}
QPdfDocument document;
document.load(pdfPath);
if (document.status() != QPdfDocument::Status::Ready) {
log(QStringLiteral("qet.addPdfPage: '%1' could not be loaded as a PDF").arg(pdfPath));
return -1;
}
const int pageCount = document.pageCount();
if (pageNumber < 1 || pageNumber > pageCount) {
log(QStringLiteral("qet.addPdfPage: '%1' has %2 page(s), no page %3")
.arg(pdfPath).arg(pageCount).arg(pageNumber));
return -1;
}
const int pageIndex = pageNumber - 1;
// PDF point = 1/72 inch, same conversion PdfPagesDialog applies.
const QSizeF pageSize = document.pagePointSize(pageIndex);
const int pixelWidth = qRound((pageSize.width() / 72.0) * dpi);
const int pixelHeight = qRound((pageSize.height() / 72.0) * dpi);
if (pixelWidth <= 0 || pixelHeight <= 0) {
log(QStringLiteral("qet.addPdfPage: could not determine page %1's size").arg(pageNumber));
return -1;
}
const QImage rendered = document.render(pageIndex, QSize(pixelWidth, pixelHeight));
if (rendered.isNull()) {
log(QStringLiteral("qet.addPdfPage: page %1 could not be rendered").arg(pageNumber));
return -1;
}
// A transparent PDF page would otherwise composite onto whatever is
// under it on the folio, unlike every other placed image.
QImage background(rendered.size(), QImage::Format_ARGB32_Premultiplied);
background.fill(Qt::white);
QPainter painter(&background);
painter.drawImage(0, 0, rendered);
painter.end();
Diagram *diagram = diagrams.at(folioIndex);
auto *item = new DiagramImageItem(QPixmap::fromImage(background));
diagram->undoStack().push(new AddGraphicsObjectCommand(item, diagram, QPointF(x, y)));
return sortedImages(folioIndex).indexOf(item);
#endif
}
namespace {
QString textSourceName(DynamicElementTextItem::TextFrom from)
{