Extend PDF engine to allow the generation of PDFs with huge pages

Qt's PDF engine previously supported only the PDF v1.4 standard, which
only allows pages of up to 200x200in (about 5x5m). This patch optionally
enables the generation of PDF v1.6-compliant files that allow the
redefinition of user space units, so that pages of up to 381x381km are
now possible. By default, generated files are compliant to v1.4 spec.
v1.6 compliance must be enabled by, e.g., calling QPrinter::setPdfVersion()
with QPrinter::PdfVersion_1_6. PDF v1.6-compliant files require Adobe
Reader 7.0 or newer (also worked with the built-in viewers in current
versions of Chrome, Firefox and Edge).

Task-number: QTBUG-69386
Change-Id: I21708e0d465d5d7d9e46ff06dd04acfe1dfb0858
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Reviewed-by: Andy Shaw <andy.shaw@qt.io>
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
This commit is contained in:
Andre de la Rocha 2018-09-06 17:16:44 +02:00
parent a87f85dbf9
commit 1bb8627f8f
6 changed files with 53 additions and 12 deletions

View File

@ -290,7 +290,8 @@ QPagedPaintDevicePrivate *QPagedPaintDevice::dd()
\value PdfVersion_A1b A PDF/A-1b compatible document is produced.
\since 5.10
\value PdfVersion_1_6 A PDF 1.6 compatible document is produced.
This value was added in Qt 5.12.
*/
/*!

View File

@ -213,7 +213,7 @@ public:
Envelope10 = Comm10E
};
enum PdfVersion { PdfVersion_1_4, PdfVersion_A1b };
enum PdfVersion { PdfVersion_1_4, PdfVersion_A1b, PdfVersion_1_6 };
// ### Qt6 Make these virtual
bool setPageLayout(const QPageLayout &pageLayout);

View File

@ -1547,7 +1547,14 @@ void QPdfEnginePrivate::writeHeader()
{
addXrefEntry(0,false);
xprintf("%%PDF-1.4\n");
static const QHash<QPdfEngine::PdfVersion, const char *> mapping {
{QPdfEngine::Version_1_4, "1.4"},
{QPdfEngine::Version_A1b, "1.4"},
{QPdfEngine::Version_1_6, "1.6"}
};
const char *verStr = mapping.value(pdfVersion, "1.4");
xprintf("%%PDF-%s\n", verStr);
xprintf("%%\303\242\303\243\n");
writeInfo();
@ -1880,6 +1887,19 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font)
}
}
qreal QPdfEnginePrivate::calcUserUnit() const
{
// PDF standards < 1.6 support max 200x200in pages (no UserUnit)
if (pdfVersion < QPdfEngine::Version_1_6)
return 1.0;
const int maxLen = qMax(currentPage->pageSize.width(), currentPage->pageSize.height());
if (maxLen <= 14400)
return 1.0; // for pages up to 200x200in (14400x14400 units) use default scaling
// for larger pages, rescale units so we can have up to 381x381km
return qMin(maxLen / 14400.0, 75000.0);
}
void QPdfEnginePrivate::writeFonts()
{
@ -1902,6 +1922,8 @@ void QPdfEnginePrivate::writePage()
uint resources = requestObject();
uint annots = requestObject();
qreal userUnit = calcUserUnit();
addXrefEntry(pages.constLast());
xprintf("<<\n"
"/Type /Page\n"
@ -1909,12 +1931,16 @@ void QPdfEnginePrivate::writePage()
"/Contents %d 0 R\n"
"/Resources %d 0 R\n"
"/Annots %d 0 R\n"
"/MediaBox [0 0 %d %d]\n"
">>\n"
"endobj\n",
"/MediaBox [0 0 %f %f]\n",
pageRoot, pageStream, resources, annots,
// make sure we use the pagesize from when we started the page, since the user may have changed it
currentPage->pageSize.width(), currentPage->pageSize.height());
currentPage->pageSize.width() / userUnit, currentPage->pageSize.height() / userUnit);
if (pdfVersion >= QPdfEngine::Version_1_6)
xprintf("/UserUnit %f\n", userUnit);
xprintf(">>\n"
"endobj\n");
addXrefEntry(resources);
xprintf("<<\n"
@ -2983,8 +3009,9 @@ void QPdfEnginePrivate::drawTextItem(const QPointF &p, const QTextItemInt &ti)
QTransform QPdfEnginePrivate::pageMatrix() const
{
qreal scale = 72./resolution;
QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, m_pageLayout.fullRectPoints().height());
qreal userUnit = calcUserUnit();
qreal scale = 72. / userUnit / resolution;
QTransform tmp(scale, 0.0, 0.0, -scale, 0.0, m_pageLayout.fullRectPoints().height() / userUnit);
if (m_pageLayout.mode() != QPageLayout::FullPageMode) {
QRect r = m_pageLayout.paintRectPixels(resolution);
tmp.translate(r.left(), r.top());

View File

@ -171,7 +171,8 @@ public:
enum PdfVersion
{
Version_1_4,
Version_A1b
Version_A1b,
Version_1_6
};
QPdfEngine();
@ -300,6 +301,7 @@ private:
void writePageRoot();
void writeFonts();
void embedFont(QFontSubset *font);
qreal calcUserUnit() const;
QVector<int> xrefPositions;
QDataStream* stream;

View File

@ -170,11 +170,17 @@ void QPdfWriter::setPdfVersion(PdfVersion version)
{
Q_D(QPdfWriter);
static const QHash<QPdfWriter::PdfVersion, QPdfEngine::PdfVersion> engineMapping {
{QPdfWriter::PdfVersion_1_4, QPdfEngine::Version_1_4},
{QPdfWriter::PdfVersion_A1b, QPdfEngine::Version_A1b},
{QPdfWriter::PdfVersion_1_6, QPdfEngine::Version_1_6}
};
if (d->pdfVersion == version)
return;
d->pdfVersion = version;
d->engine->setPdfVersion(d->pdfVersion == QPdfWriter::PdfVersion_1_4 ? QPdfEngine::Version_1_4 : QPdfEngine::Version_A1b);
d->engine->setPdfVersion(engineMapping.value(version, QPdfEngine::Version_1_4));
}
/*!

View File

@ -149,7 +149,12 @@ void QPrinterPrivate::initEngines(QPrinter::OutputFormat format, const QPrinterI
printEngine = ps->createNativePrintEngine(printerMode, printerName);
paintEngine = ps->createPaintEngine(printEngine, printerMode);
} else {
const auto pdfEngineVersion = (pdfVersion == QPrinter::PdfVersion_1_4 ? QPdfEngine::Version_1_4 : QPdfEngine::Version_A1b);
static const QHash<QPrinter::PdfVersion, QPdfEngine::PdfVersion> engineMapping {
{QPrinter::PdfVersion_1_4, QPdfEngine::Version_1_4},
{QPrinter::PdfVersion_A1b, QPdfEngine::Version_A1b},
{QPrinter::PdfVersion_1_6, QPdfEngine::Version_1_6}
};
const auto pdfEngineVersion = engineMapping.value(pdfVersion, QPdfEngine::Version_1_4);
QPdfPrintEngine *pdfEngine = new QPdfPrintEngine(printerMode, pdfEngineVersion);
paintEngine = pdfEngine;
printEngine = pdfEngine;