Commit f4013bf0 authored by Michael Winkelmann's avatar Michael Winkelmann

Refactorings, support for GraphicalEffects

parent fe2aaa30
QT += qml quick gui printsupport
CONFIG -= app_bundle
SOURCES += $$PWD/qmlprinter.cpp
HEADERS += $$PWD/qmlprinter.h
......
......@@ -7,6 +7,7 @@
#include <QQuickView>
#include <QCommandLineParser>
#include <QTimer>
#include "qmlprinter.h"
......@@ -20,21 +21,18 @@ int main(int ac, char *av[]) {
parser.addOptions(
{{{"input", "i"}, "Input QML file", "input"},
{{"output", "o"}, "Output PDF file", "output", "output.pdf"}});
QCommandLineOption showOption({"show", "s"}, "Show PDF file");
parser.addOption(showOption);
QCommandLineOption showPresentation({"presentation", "p"},
"Show Presentation");
parser.addOption(showPresentation);
{{"output", "o"}, "Output PDF file", "output", "output.pdf"},
{{"importpath", "I"}, "QML import path", "importpath" },
{{"show-pdf", "p"}, "Show PDF file", "show-qml" },
{{"show-qml", "q"}, "Show QML Output", "show-pdf" }
});
parser.process(app);
auto inputQml = parser.value("input");
auto outputPdf = parser.value("output");
auto showPdf = parser.isSet(showOption);
auto showPres = parser.isSet(showPresentation);
auto showPdf = parser.isSet("show-pdf");
auto showQml = parser.isSet("show-qml");
QDir outputPdfDir;
......@@ -46,22 +44,32 @@ int main(int ac, char *av[]) {
}
auto *view = new QQuickView;
view->setSource(QUrl::fromLocalFile(inputQml));
view->show();
view->create();
QmlPrinter printer;
if (!printer.printPdf(outputPdfDir.absolutePath(), view->rootObject())) {
qInfo() << app.tr("No PDF output was produced!");
return EXIT_FAILURE;
}
view->engine()->addImportPath(".");
QStringList importPaths = parser.values("importpath");
for (auto& path : importPaths)
view->engine()->addImportPath(path);
qInfo() << app.tr("Writing PDF to %1").arg(outputPdfDir.absolutePath());
if (showPres) {
view->rootObject()->setProperty("currentSlide", 0);
view->setSource(QUrl::fromLocalFile(inputQml));
view->setGeometry(0,0, view->rootObject()->width(), view->rootObject()->height());
if (showQml)
view->show();
}
if (showPdf)
QDesktopServices::openUrl(QUrl::fromLocalFile(outputPdfDir.absolutePath()));
QTimer::singleShot(10000,[=]() {
QmlPrinter printer;
qInfo() << QObject::tr("Writing PDF to %1").arg(outputPdfDir.absolutePath());
if (!printer.printPdf(outputPdfDir.absolutePath(), view->rootObject())) {
qInfo() << QObject::tr("No PDF output was produced!");
}
qGuiApp->quit();
if (showPdf)
QDesktopServices::openUrl(QUrl::fromLocalFile(outputPdfDir.absolutePath()));
});
return showPres ? app.exec() : EXIT_SUCCESS;
return app.exec();
}
......@@ -7,6 +7,8 @@
#include <QQuickItem>
#include <QQuickWindow>
#include <QtMath>
#include <QQuickItemGrabResult>
#include <QGuiApplication>
Q_LOGGING_CATEGORY(lc, "qt.qmlprinter")
......@@ -22,46 +24,40 @@ void QmlPrinter::changePrinterOrientation(QPrinter &printer, const int &width,
printer.setOrientation(QPrinter::Portrait);
}
bool QmlPrinter::printPdf(const QString &location, QQuickItem *presentation) {
if (!presentation) {
return false;
}
QVector<QQuickItem *> slides, masterSlideElements;
presentation->setProperty(
"allowDelay",
QVariant(false)); // Disable partial reveals on slide pages
for (auto &item : presentation->childItems()) {
if (item->property("isMaster").toBool()) {
masterSlideElements.push_back(item);
} else {
if (item->metaObject()->className() != QStringLiteral("QQuickRepeater"))
slides.push_back(item);
}
bool QmlPrinter::printPdf(const QString &location, QQuickItem *rootItem) {
if (!rootItem) {
return false;
}
if (slides.length() == 0) {
qCWarning(lc) << QObject::tr("No pages in %1, no PDF produced!").arg(location);
return false;
}
auto width = rootItem->property("width").toInt();
auto height = rootItem->property("height").toInt();
auto width = presentation->property("width").toInt();
auto height = presentation->property("height").toInt();
QPrinter printer;
QPrinter printer(QPrinter::HighResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(location);
printer.setFullPage(true);
printer.setPageLayout(QPageLayout(QPageSize(QSize(height, width)),
QPageLayout::Portrait,
QMarginsF(0, 0, 0, 0), QPageLayout::Pica));
printer.setResolution(72);
printer.setResolution(resolution());
// Change the printer orientation based on the presentation
// This needs to be called before the painting is started as it will only take
// effect after newPage is called (painter.begin() calls this method)
changePrinterOrientation(printer, width, height);
// SubItemStrategy strategy;
PrintStrategy strategy = [](QQuickItem* root, ItemList& items, ItemList& masterItems) {
items.push_back({ root, QVariantMap() });
return true;
};
return printItems(printer,rootItem,strategy);
}
bool QmlPrinter::printItems(QPrinter& printer, ItemList const& items,
ItemList const& masterItems) const {
QPainter painter;
// It's possible to fail here for example if the file location does not allow
......@@ -70,34 +66,56 @@ bool QmlPrinter::printPdf(const QString &location, QQuickItem *presentation) {
return false;
}
for (int i = 0; i < slides.length(); ++i) {
QQuickItem *pageObject = slides.at(i);
for (size_t i = 0; i < items.size(); ++i) {
// Paint master slide elements
for (auto &masterSlideElement : masterSlideElements) {
masterSlideElement->setProperty("slideNumber",i+1);
paintItem(masterSlideElement, pageObject->window(), &painter);
}
auto item = items.at(i).item;
if (!item->property("noMaster").toBool()) {
// Paint master slide elements
for (auto &masterItem : masterItems) {
masterItem.item->setProperty("slideNumber",int(i+1));
paintItem(masterItem, &painter);
}
}
// Paint current slide
paintItem(pageObject, pageObject->window(), &painter);
// Paint current slide
paintItem(items.at(i), &painter);
// We need to lookahead so we can setup the printer orientation for the next
// item and add a new page to the printer
int next = i + 1;
if (next < slides.length()) {
// We need to lookahead so we can setup the printer orientation for the next
// item and add a new page to the printer
size_t next = i + 1;
if (next < items.size()) {
changePrinterOrientation(printer,
int(slides.at(next)->width()),
int(slides.at(next)->height()));
int(items.at(next).item->width()),
int(items.at(next).item->height()));
printer.newPage();
}
}
return true;
}
bool QmlPrinter::printItems(QPrinter &printer, QQuickItem *rootItem, PrintStrategy strategy) const
{
ItemList items, masterItems;
if (!strategy(rootItem,items,masterItems)) {
return false;
}
return printItems(printer,items,masterItems);
}
int QmlPrinter::resolution() const
{
return m_options.count("resolution") ? m_options.value("resolution").toInt() : 72;
}
void QmlPrinter::setResolution(int resolution)
{
m_options["resolution"] = resolution;
}
void QmlPrinter::paintItem(QQuickItem *item, QQuickWindow *window,
QPainter *painter) {
QPainter *painter) const {
if (!item || !item->isVisible())
return;
......@@ -111,6 +129,7 @@ void QmlPrinter::paintItem(QQuickItem *item, QQuickWindow *window,
painter->setClipRect(item->clipRect());
}
// This is a bit special case as we need to use childItems instead of children
if (inherits(item->metaObject(), "QQuickListView")) {
drawChildren = false;
......@@ -137,13 +156,32 @@ void QmlPrinter::paintItem(QQuickItem *item, QQuickWindow *window,
} else if (inherits(item->metaObject(), "QQuickCanvasItem")) {
paintQQuickCanvasItem(item, window, painter);
} else {
// Fallback to screen capture if we are unable to parse the data
QRectF rect(0,0,item->width(),item->height());
if (window != nullptr) {
QImage image = window->grabWindow();
painter->drawImage(rect, image, rect);
auto imageGrabResult = item->grabToImage(rect.size().toSize());
bool ready = false;
if (imageGrabResult) {
QObject::connect( imageGrabResult.data(), &QQuickItemGrabResult::ready,
[&]() {
painter->drawImage(rect, imageGrabResult->image(), rect);
ready = true;
});
drawChildren = false;
} else {
ready = true;
drawChildren = false;
}
while(!ready) { qGuiApp->processEvents(); }
// windowImage.save(QString(item->metaObject()->className()) + ".jpg");
}
drawChildren = false;
// drawChildren = false;
}
}
......@@ -161,8 +199,16 @@ void QmlPrinter::paintItem(QQuickItem *item, QQuickWindow *window,
painter->restore();
}
void QmlPrinter::paintItem(const ItemWithProperties& item, QPainter *painter) const {
auto& props = item.properties;
for (const auto& key : props.keys()) {
item.item->setProperty(key.toStdString().c_str(),props.value(key));
}
paintItem(item.item,item.item->window(),painter);
}
void QmlPrinter::paintQQuickCanvasItem(QQuickItem *item, QQuickWindow *window,
QPainter *painter) {
QPainter *painter) const {
// No point in continuing as we are unable to grab the image
if (window == nullptr)
return;
......@@ -172,7 +218,7 @@ void QmlPrinter::paintQQuickCanvasItem(QQuickItem *item, QQuickWindow *window,
painter->drawImage(0, 0, image, 0, 0, rect.width(), rect.height());
}
void QmlPrinter::paintQQuickRectangle(QQuickItem *item, QPainter *painter) {
void QmlPrinter::paintQQuickRectangle(QQuickItem *item, QPainter *painter) const {
const QRectF rect(0, 0, item->width(), item->height());
const QColor color = item->property("color").value<QColor>();
......@@ -198,7 +244,7 @@ void QmlPrinter::paintQQuickRectangle(QQuickItem *item, QPainter *painter) {
}
}
void QmlPrinter::paintQQuickText(QQuickItem *item, QPainter *painter) {
void QmlPrinter::paintQQuickText(QQuickItem *item, QPainter *painter) const {
const QRectF rect(0, 0, item->width(), item->height());
const QFont font = item->property("font").value<QFont>();
const QString text = item->property("text").value<QString>();
......@@ -244,7 +290,7 @@ void QmlPrinter::paintQQuickText(QQuickItem *item, QPainter *painter) {
layout->draw(painter, context);
}
void QmlPrinter::paintQQuickImage(QQuickItem *item, QPainter *painter) {
void QmlPrinter::paintQQuickImage(QQuickItem *item, QPainter *painter) const {
const QUrl url = item->property("source").value<QUrl>();
const int fillMode = item->property("fillMode").value<int>();
......@@ -341,3 +387,33 @@ QMatrix QmlPrinter::transformMatrix(QQuickItem *item) {
return t.toAffine();
}
bool SubItemStrategy::operator()(QQuickItem *rootItem, ItemList &items, ItemList &masterItems)
{
rootItem->setProperty(
"allowDelay",
QVariant(false)); // Disable partial reveals on slide pages
for (auto &item : rootItem->childItems()) {
if (item->property("isMaster").toBool()) {
masterItems.push_back({item, QVariantMap()});
} else {
if (item->metaObject()->className() != QStringLiteral("QQuickRepeater"))
items.push_back({item, QVariantMap()});
}
}
return !items.empty();
}
StackLayoutStrategy::StackLayoutStrategy(const QVariantMap & options) :
m_options(options)
{
}
bool StackLayoutStrategy::operator()(QQuickItem *rootItem,
ItemList & items,
ItemList & masterItems)
{
}
......@@ -2,6 +2,8 @@
#define QMLPRINTER_H
#include <QObject>
#include <QVariantMap>
#include <functional>
QT_BEGIN_NAMESPACE
class QQuickItem;
......@@ -11,29 +13,68 @@ class QPrinter;
class QPrinterInfo;
QT_END_NAMESPACE
struct ItemWithProperties {
QQuickItem* item;
QVariantMap properties;
};
using ItemList = std::vector<ItemWithProperties>;
using PrintStrategy = std::function<bool(QQuickItem*,ItemList&,ItemList&)>;
struct SubItemStrategy {
bool operator()(QQuickItem*, ItemList&, ItemList&);
};
struct StackLayoutStrategy {
StackLayoutStrategy(QVariantMap const&options);
bool operator()(QQuickItem*, ItemList&, ItemList&);
private:
QVariantMap m_options;
};
class QmlPrinter {
Q_GADGET
public:
enum class Strategy {
Items,
StackLayout,
State
};
Q_ENUM(Strategy)
explicit QmlPrinter();
virtual ~QmlPrinter();
bool printPdf(const QString &location, QQuickItem *presentation);
void addPrintableItem(const QString &item);
private:
QList<QString> printableItems;
bool printPdf(const QString &location, QQuickItem *rootItem);
bool printItems(QPrinter& printer, ItemList const& items, ItemList const& masterItems) const;
bool printItems(QPrinter& printer, QQuickItem *rootItem, PrintStrategy) const;
int resolution() const;
void setResolution(int resolution);
private:
static QMatrix transformMatrix(QQuickItem *);
void paintItem(QQuickItem *item, QQuickWindow *window, QPainter *painter);
void paintQQuickRectangle(QQuickItem *item, QPainter *painter);
void paintQQuickText(QQuickItem *item, QPainter *painter);
void paintQQuickImage(QQuickItem *item, QPainter *painter);
void paintItem(QQuickItem *item, QQuickWindow *window, QPainter *painter) const;
void paintItem(ItemWithProperties const&, QPainter *painter) const;
void paintQQuickRectangle(QQuickItem *item, QPainter *painter) const;
void paintQQuickText(QQuickItem *item, QPainter *painter) const;
void paintQQuickImage(QQuickItem *item, QPainter *painter) const;
void paintQQuickCanvasItem(QQuickItem *item, QQuickWindow *window,
QPainter *painter);
QPainter *painter) const;
bool inherits(const QMetaObject *metaObject, const QString &name);
static bool inherits(const QMetaObject *metaObject, const QString &name);
void changePrinterOrientation(QPrinter &printer, const int &width,
static void changePrinterOrientation(QPrinter &printer, const int &width,
const int &height);
Strategy m_strategy = Strategy::Items;
QVariantMap m_options;
};
#endif // HURPRINTER_H
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment