qmlprinter.cpp 11.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
/*
 * Copyright (c) 2014 Taneli Peltoniemi <taneli.peltoniemi@gmail.com>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "qmlprinter.h"

11
#include <QGraphicsView>
12
#include <QtMath>
13

14 15
QmlPrinter::QmlPrinter(QObject *parent)
    : QObject(parent)
16 17 18
{
}

19
QmlPrinter::~QmlPrinter() {}
20

21
void QmlPrinter::changePrinterOrientation(QPrinter &printer, const int &width, const int &height)
22
{
23
    if (width > height)
24 25 26 27
        printer.setOrientation(QPrinter::Landscape);
    else
        printer.setOrientation(QPrinter::Portrait);
}
28

29
bool QmlPrinter::printPdf(const QString &location, QQuickItem *presentation)
30
{
31 32 33 34 35 36 37 38 39 40 41 42 43
    QVector<QQuickItem *> slides, masterSlideElements;
    presentation->setProperty("allowDelay",
                              QVariant(false)); // Disable partial reveals on slide pages

    for (auto &item : presentation->childItems()) {
        if (item->property("isSlide").toBool()) {
            slides.push_back(item);
        } else {
            masterSlideElements.push_back(item);
        }
    }

    if (slides.length() == 0) {
44 45
        return false;
    }
46 47 48 49

    auto width = presentation->property("width").toFloat();
    auto height = presentation->property("height").toFloat();

50 51 52 53 54
    QPrinter printer;
    printer.setOutputFormat(QPrinter::PdfFormat);
    printer.setOutputFileName(location);
    printer.setFullPage(true);

55 56 57 58 59
    printer.setPageLayout(QPageLayout(QPageSize(QSize(height, width)), QPageLayout::Portrait,
                                      QMarginsF(0, 0, 0, 0), QPageLayout::Pica));
    printer.setResolution(72);

    // Change the printer orientation based on the presentation
60 61
    // 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)
62
    changePrinterOrientation(printer, width, height);
63

64
    QPainter painter;
65
    // It's possible to fail here for example if the file location does not allow writing
66
    if (!painter.begin(&printer)) {
67 68
        return false;
    }
69

70 71
    for (int i = 0; i < slides.length(); ++i) {
        QQuickItem *pageObject = slides.at(i);
72

73 74 75
        // Paint master slide elements
        for (auto &masterSlideElement : masterSlideElements) {
            paintItem(masterSlideElement, pageObject->window(), &painter);
76 77
        }

78
        // Paint current slide
79 80 81 82 83
        paintItem(pageObject, pageObject->window(), &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;
84 85 86 87 88
        if (next < slides.length()) {
            // changePrinterOrientation(printer, slides.at(next)->width(),
            // slides.at(next)->height());
            static const QMetaObject *meta = presentation->metaObject();
            meta->invokeMethod(presentation, "goToNextSlide");
89 90 91
            printer.newPage();
        }
    }
92

93
    painter.end();
94
    return true;
95 96
}

97 98
void QmlPrinter::paintItem(QQuickItem *item, QQuickWindow *window, QPainter *painter)
{
99
    if (!item || !item->isVisible())
100 101
        return;

102
    bool drawChildren = true;
103 104

    // This is a bit special case as we need to use childItems instead of children
105
    if (inherits(item->metaObject(), "QQuickListView")) {
106
        drawChildren = false;
107 108
        QList<QQuickItem *> childItems = item->childItems();
        if (childItems.length() > 0) {
109 110
            // First item is the QML ListView
            QQuickItem *listView = childItems.at(0);
111
            if (listView != nullptr) {
112
                // Draw the child items of the QML ListView
113 114
                QList<QQuickItem *> listViewChildren = listView->childItems();
                foreach (QQuickItem *children, listViewChildren) {
115 116 117 118
                    paintItem(children, window, painter);
                }
            }
        }
119
    } else if (isCustomPrintItem(item->metaObject()->className())) {
120
        painter->save();
121
        if (item->clip()) {
122 123 124 125
            painter->setClipping(true);
            painter->setClipRect(item->clipRect());
        }

126 127 128 129 130 131 132 133
        const int boundingMargin = 5;
        QRectF boundingRect;
        boundingRect.setTop(item->boundingRect().top() - boundingMargin);
        boundingRect.setLeft(item->boundingRect().left() - boundingMargin);
        boundingRect.setHeight(item->boundingRect().height() + boundingMargin * 2);
        boundingRect.setWidth(item->boundingRect().width() + boundingMargin * 2);

        const QRectF rect = item->mapRectToScene(boundingRect);
134
        QImage image = window->grabWindow();
135 136
        painter->drawImage(rect.x(), rect.y(), image, rect.x(), rect.y(), rect.width(),
                           rect.height());
137
        painter->restore();
138
        drawChildren = false;
139
    } else if (item->flags().testFlag(QQuickItem::ItemHasContents)) {
140
        painter->save();
141
        if (item->clip()) {
142 143 144
            painter->setClipping(true);
            painter->setClipRect(item->clipRect());
        }
145
        if (inherits(item->metaObject(), "QQuickRectangle")) {
146
            paintQQuickRectangle(item, painter);
147
        } else if (inherits(item->metaObject(), "QQuickText")) {
148
            paintQQuickText(item, painter);
149
        } else if (inherits(item->metaObject(), "QQuickImage")) {
150
            paintQQuickImage(item, painter);
151
        } else if (inherits(item->metaObject(), "QQuickCanvasItem")) {
152 153 154 155
            paintQQuickCanvasItem(item, window, painter);
        } else {
            // Fallback to screen capture if we are unable to parse the data
            QRect rect = item->mapRectFromScene(item->boundingRect()).toRect();
156
            if (window != nullptr) {
157 158
                QImage image = window->grabWindow();

159 160
                painter->drawImage(rect, image,
                                   QRect(rect.x(), rect.y(), rect.width(), rect.height()));
161
            }
162
            drawChildren = false;
163 164 165
        }
        painter->restore();
    }
166 167 168 169 170 171 172 173 174
    if (drawChildren) {

        auto items = item->childItems();
        std::sort(items.begin(),items.end(),[](QQuickItem* lhs, QQuickItem* rhs) {
           return lhs->z() < rhs->z();
        });

        for (auto item : items)
            paintItem(item, window, painter);
175 176 177 178 179 180
    }
}

void QmlPrinter::paintQQuickCanvasItem(QQuickItem *item, QQuickWindow *window, QPainter *painter)
{
    // No point in continuing as we are unable to grab the image
181
    if (window == nullptr)
182 183 184 185 186 187 188 189 190 191 192 193
        return;

    const QRectF rect = item->mapRectToScene(item->boundingRect());

    QImage image = window->grabWindow();
    painter->drawImage(rect.x(), rect.y(), image, rect.x(), rect.y(), rect.width(), rect.height());
}

void QmlPrinter::paintQQuickRectangle(QQuickItem *item, QPainter *painter)
{
    const QRect rect = item->mapRectToScene(item->boundingRect()).toRect();
    const QColor color = item->property("color").value<QColor>();
194
    const QObject *border = item->property("border").value<QObject *>();
195 196 197
    const qreal border_width = border->property("width").value<qreal>();
    const QColor border_color = border->property("color").value<QColor>();
    const qreal radius = item->property("radius").value<qreal>();
198
    const qreal opacity = item->property("opacity").value<qreal>();
199 200

    painter->setBrush(color);
201
    painter->setOpacity(opacity);
202

203
    if (border_width > 0 and not(border_width == 1 and border_color == QColor(Qt::black))) {
204 205 206 207 208
        painter->setPen(QPen(border_color, border_width));
    } else {
        painter->setPen(Qt::NoPen);
    }

209
    if (radius > 0) {
210 211 212 213 214 215 216 217
        painter->drawRoundedRect(rect, radius, radius);
    } else {
        painter->drawRect(rect);
    }
}

void QmlPrinter::paintQQuickText(QQuickItem *item, QPainter *painter)
{
218
    const QRectF rect = item->mapRectToScene(item->boundingRect());
219 220 221 222 223 224
    const QFont font = item->property("font").value<QFont>();
    const QString text = item->property("text").value<QString>();
    const QColor color = item->property("color").value<QColor>();
    const int wrapMode = item->property("wrapMode").value<int>();
    int textFormat = item->property("textFormat").value<int>();
    const int horizontalAlignment = item->property("horizontalAlignment").value<int>();
225
    const int verticalAlignment = item->property("verticalAlignment").value<int>();
226

227 228 229
    const int elide = item->property("elide").value<int>();
    Qt::TextElideMode elideMode = static_cast<Qt::TextElideMode>(elide);

230 231
    QTextOption textOption;
    textOption.setWrapMode(QTextOption::WrapMode(wrapMode));
232
    textOption.setAlignment(static_cast<Qt::Alignment>(horizontalAlignment | verticalAlignment));
233

234
    QFontMetrics fm(font);
235

236 237
    textFormat = Qt::RichText;
    if (textFormat == Qt::AutoText) {
238 239 240
        textFormat = Qt::mightBeRichText(text) ? 4 : Qt::PlainText;
    }

241 242 243 244
    QRectF textRect = rect;
    if (item->rotation() != 0) {
        int xc = rect.x() + rect.width() / 2;
        int yc = rect.y() + rect.height() / 2;
245

246 247 248
        QMatrix matrix;
        matrix.translate(xc, yc);
        matrix.rotate(item->rotation());
249

250 251
        painter->setMatrix(matrix);
        painter->setMatrixEnabled(true);
252

253 254 255
        double PI = 3.14159265358979323846;
        double cosine = cos(static_cast<double>(item->rotation()) * PI / 180.0);
        double sine = sin(static_cast<double>(item->rotation()) * PI / 180.0);
256

257 258 259
        textRect = QRectF(-abs(sine) * rect.height() * 0.5, abs(cosine) * rect.width() * 0.5,
                          rect.height(), rect.width());
    }
260

261 262 263 264 265 266 267
    QTextDocument document;
    document.setPageSize(item->size());
    document.setDocumentMargin(0.0);
    document.setUseDesignMetrics(true);
    document.setTextWidth(textRect.width());
    document.setDefaultTextOption(textOption);
    document.setDefaultFont(font);
268

269 270 271 272
    if (textFormat == Qt::PlainText)
        document.setPlainText(text);
    else
        document.setHtml(text);
273

274 275
    QAbstractTextDocumentLayout::PaintContext context;
    context.palette.setColor(QPalette::Text, color);
276

277 278 279 280
    QAbstractTextDocumentLayout *layout = document.documentLayout();
    painter->translate(textRect.topLeft());
    painter->setRenderHint(QPainter::Antialiasing, true);
    layout->draw(painter, context);
281 282 283 284 285 286 287 288 289
}

void QmlPrinter::paintQQuickImage(QQuickItem *item, QPainter *painter)
{
    const QUrl url = item->property("source").value<QUrl>();
    const int fillMode = item->property("fillMode").value<int>();

    QImage image(url.toLocalFile());

290
    QRect rect = item->mapRectToScene(item->boundingRect()).toRect();
291 292
    QRect sourceRect(0, 0, image.width(), image.height());

293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
    switch (fillMode) {
    default:
        qWarning() << "QuickItemPainter::paintQuickImage unimplemented fill mode: " << fillMode;
    case 0: // Image.Stretch
        break;
    case 1: { // Image.PreserveAspectFit
        QSize size = sourceRect.size();
        size.scale(rect.width(), rect.height(), Qt::KeepAspectRatio);
    } break;
    case 6: { // Image.Pad
        if (sourceRect.width() > rect.width()) {
            rect.setWidth(sourceRect.width());
        } else {
            sourceRect.setWidth(rect.width());
        }
        if (sourceRect.height() > rect.height()) {
            rect.setHeight(sourceRect.height());
        } else {
            sourceRect.setHeight(rect.height());
        }
    } break;
314 315 316 317 318 319
    }
    painter->drawImage(rect, image, sourceRect);
}

bool QmlPrinter::inherits(const QMetaObject *metaObject, const QString &name)
{
320
    if (metaObject->className() == name) {
321
        return true;
322
    } else if (metaObject->superClass()) {
323 324 325 326
        return inherits(metaObject->superClass(), name);
    }
    return false;
}
327 328 329 330 331 332 333 334 335

void QmlPrinter::addPrintableItem(const QString &item)
{
    printableItems.push_back(item);
}

bool QmlPrinter::isCustomPrintItem(const QString &item)
{
    QListIterator<QString> it(printableItems);
336
    while (it.hasNext()) {
337
        QString printableItem = it.next();
338
        if (item.contains(printableItem))
339 340 341 342
            return true;
    }
    return false;
}