Commit f4013bf0 authored by Michael Winkelmann's avatar Michael Winkelmann

Refactorings, support for GraphicalEffects

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