Warning, file /office/calligra/libs/textlayout/KoStyleThumbnailer.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002  * Copyright (C) 2006, 2009 Thomas Zander <zander@kde.org>
0003  * Copyright (C) 2007 Sebastian Sauer <mail@dipe.org>
0004  * Copyright (C) 2007 Pierre Ducroquet <pinaraf@gmail.com>
0005  * Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
0006  * Copyright (C) 2009,2011 KO GmbH <cbo@kogmbh.com>
0007  * Copyright (C) 2011-2012 Pierre Stirnweiss <pstirnweiss@googlemail.com>
0008  * Copyright (C) 2012 Gopalakrishna Bhat A <gopalakbhat@gmail.com>
0009  *
0010  * This library is free software; you can redistribute it and/or
0011  * modify it under the terms of the GNU Library General Public
0012  * License as published by the Free Software Foundation; either
0013  * version 2 of the License, or (at your option) any later version.
0014  *
0015  * This library is distributed in the hope that it will be useful,
0016  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0017  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0018  * Library General Public License for more details.
0019  *
0020  * You should have received a copy of the GNU Library General Public License
0021  * along with this library; see the file COPYING.LIB.  If not, write to
0022  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0023  * Boston, MA 02110-1301, USA.
0024  */
0025 
0026 #include "KoStyleThumbnailer.h"
0027 
0028 #include "KoParagraphStyle.h"
0029 #include "KoCharacterStyle.h"
0030 #include "KoTextDocumentLayout.h"
0031 #include "KoTextLayoutRootArea.h"
0032 #include "FrameIterator.h"
0033 
0034 #include <stdint.h>
0035 
0036 #include <klocalizedstring.h>
0037 
0038 #include <QCache>
0039 #include <QFont>
0040 #include <QImage>
0041 #include <QPainter>
0042 #include <QRect>
0043 #include <QTextBlock>
0044 #include <QTextCursor>
0045 #include <QTextLength>
0046 
0047 #include <TextLayoutDebug.h>
0048 
0049 extern int qt_defaultDpiX();
0050 extern int qt_defaultDpiY();
0051 
0052 class Q_DECL_HIDDEN KoStyleThumbnailer::Private
0053 {
0054 public:
0055     Private() :
0056         thumbnailHelperDocument(new QTextDocument),
0057         documentLayout(new KoTextDocumentLayout(thumbnailHelperDocument)),
0058         defaultSize(QSize(250, 48))
0059     {
0060         thumbnailHelperDocument->setDocumentLayout(documentLayout);
0061     }
0062 
0063     ~Private()
0064     {
0065         delete documentLayout;
0066         delete thumbnailHelperDocument;
0067     }
0068 
0069     QTextDocument *thumbnailHelperDocument;
0070     KoTextDocumentLayout *documentLayout;
0071     QCache<QString, QImage> thumbnailCache; // cache of QImage representations of the styles
0072     QSize defaultSize;
0073     QString thumbnailText;
0074 };
0075 
0076 KoStyleThumbnailer::KoStyleThumbnailer()
0077         : d(new Private())
0078 {
0079 }
0080 
0081 KoStyleThumbnailer::~KoStyleThumbnailer()
0082 {
0083     delete d;
0084 }
0085 
0086 QImage KoStyleThumbnailer::thumbnail(KoParagraphStyle *style, const QSize &_size, bool recreateThumbnail, KoStyleThumbnailerFlags flags)
0087 {
0088     if ((flags & UseStyleNameText)  && (!style || style->name().isNull())) {
0089         return QImage();
0090     } else if ((! (flags & UseStyleNameText)) && d->thumbnailText.isEmpty()) {
0091         return QImage();
0092     }
0093 
0094     const QSize &size = (!_size.isValid() || _size.isNull()) ? d->defaultSize : _size;
0095 
0096     QString imageKey = "p_" + QString::number(reinterpret_cast<uintptr_t>(style)) + "_" + QString::number(size.width()) + "_" + QString::number(size.height());
0097 
0098     if (!recreateThumbnail && d->thumbnailCache.object(imageKey)) {
0099         return QImage(*(d->thumbnailCache.object(imageKey)));
0100     }
0101 
0102     QImage *im = new QImage(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
0103     im->fill(QColor(Qt::transparent).rgba());
0104 
0105     KoParagraphStyle *clone = style->clone();
0106     //TODO: make the following real options
0107     //we ignore these properties when the thumbnail would not be sufficient to preview properly the whole paragraph with margins.
0108     clone->setMargin(QTextLength(QTextLength::FixedLength, 0));
0109     clone->setPadding(0);
0110     //
0111     QTextCursor cursor(d->thumbnailHelperDocument);
0112     cursor.select(QTextCursor::Document);
0113     cursor.setBlockFormat(QTextBlockFormat());
0114     cursor.setBlockCharFormat(QTextCharFormat());
0115     cursor.setCharFormat(QTextCharFormat());
0116     QTextBlock block = cursor.block();
0117     clone->applyStyle(block, true);
0118 
0119     QTextCharFormat format;
0120     // Default to black as text color, to match what KoTextLayoutArea::paint(...)
0121     // does, setting solid black if no brush is set. Otherwise the UI text color
0122     // would be used, which might be too bright with dark UI color schemes
0123     format.setForeground(QColor(Qt::black));
0124     clone->KoCharacterStyle::applyStyle(format);
0125     if (flags & UseStyleNameText) {
0126         cursor.insertText(clone->name(), format);
0127     } else {
0128         cursor.insertText(d->thumbnailText, format);
0129     }
0130     layoutThumbnail(size, im, flags);
0131     // Make a copy of the image before inserting in the cache
0132     QImage res = QImage(*im);
0133     // Because on inserting, QCache can decide to delete the object immediately
0134     d->thumbnailCache.insert(imageKey, im);
0135 
0136     delete clone;
0137     return res;
0138 }
0139 
0140 QImage KoStyleThumbnailer::thumbnail(KoCharacterStyle *characterStyle, KoParagraphStyle *paragraphStyle, const QSize &_size, bool recreateThumbnail, KoStyleThumbnailerFlags flags)
0141 {
0142     if ((flags & UseStyleNameText)  && (!characterStyle || characterStyle->name().isNull())) {
0143         return QImage();
0144     } else if ((! (flags & UseStyleNameText)) && d->thumbnailText.isEmpty()) {
0145         return QImage();
0146     }
0147     else if (characterStyle == 0) {
0148         return QImage();
0149     }
0150 
0151     const QSize &size = (!_size.isValid() || _size.isNull()) ? d->defaultSize : _size;
0152 
0153     QString imageKey = "c_" + QString::number(reinterpret_cast<uintptr_t>(characterStyle)) + "_"
0154                      + "p_" + QString::number(reinterpret_cast<uintptr_t>(paragraphStyle)) + "_"
0155                      + QString::number(size.width()) + "_" + QString::number(size.height());
0156 
0157     if (!recreateThumbnail && d->thumbnailCache.object(imageKey)) {
0158         return QImage(*(d->thumbnailCache.object(imageKey)));
0159     }
0160 
0161     QImage *im = new QImage(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
0162     im->fill(QColor(Qt::transparent).rgba());
0163 
0164     QTextCursor cursor(d->thumbnailHelperDocument);
0165     QTextCharFormat format;
0166     // Default to black as text color, to match what KoTextLayoutArea::paint(...)
0167     // does, setting solid black if no brush is set. Otherwise the UI text color
0168     // would be used, which might be too bright with dark UI color schemes
0169     format.setForeground(QColor(Qt::black));
0170     KoCharacterStyle *characterStyleClone = characterStyle->clone();
0171     characterStyleClone->applyStyle(format);
0172     cursor.select(QTextCursor::Document);
0173     cursor.setBlockFormat(QTextBlockFormat());
0174     cursor.setBlockCharFormat(QTextCharFormat());
0175     cursor.setCharFormat(QTextCharFormat());
0176 
0177     if (paragraphStyle) {
0178         KoParagraphStyle *paragraphStyleClone = paragraphStyle->clone();
0179        // paragraphStyleClone->KoCharacterStyle::applyStyle(format);
0180         QTextBlock block = cursor.block();
0181         paragraphStyleClone->applyStyle(block, true);
0182         delete paragraphStyleClone;
0183         paragraphStyleClone = 0;
0184     }
0185 
0186     if (flags & UseStyleNameText) {
0187         cursor.insertText(characterStyleClone->name(), format);
0188     } else {
0189         cursor.insertText(d->thumbnailText, format);
0190     }
0191 
0192     layoutThumbnail(size, im, flags);
0193     QImage res = QImage(*im);
0194     d->thumbnailCache.insert(imageKey, im);
0195     delete characterStyleClone;
0196     return res;
0197 }
0198 
0199 void KoStyleThumbnailer::setThumbnailSize(const QSize &size)
0200 {
0201     d->defaultSize = size;
0202 }
0203 
0204 void KoStyleThumbnailer::layoutThumbnail(const QSize &size, QImage *im, KoStyleThumbnailerFlags flags)
0205 {
0206     QPainter p(im);
0207     d->documentLayout->removeRootArea();
0208     KoTextLayoutRootArea rootArea(d->documentLayout);
0209     rootArea.setReferenceRect(0, size.width() * 72.0 / qt_defaultDpiX(), 0, 1E6);
0210     rootArea.setNoWrap(1E6);
0211 
0212     FrameIterator frameCursor(d->thumbnailHelperDocument->rootFrame());
0213     rootArea.layoutRoot(&frameCursor);
0214 
0215     QSizeF documentSize = rootArea.boundingRect().size();
0216     documentSize.setWidth(documentSize.width() * qt_defaultDpiX() / 72.0);
0217     documentSize.setHeight(documentSize.height() * qt_defaultDpiY() / 72.0);
0218     if (documentSize.width() > size.width() || documentSize.height() > size.height()) {
0219         //calculate the space needed for the font size indicator (should the preview be too big with the style's font size
0220         QTextCursor cursor(d->thumbnailHelperDocument);
0221         cursor.select(QTextCursor::Document);
0222         QString sizeHint = "\t" + QString::number(cursor.charFormat().fontPointSize()) + "pt";
0223         p.save();
0224         QFont sizeHintFont = p.font();
0225         sizeHintFont.setPointSize(8);
0226         p.setFont(sizeHintFont);
0227         QRectF sizeHintRect(p.boundingRect(0, 0, 1, 1, Qt::AlignCenter, sizeHint));
0228         p.restore();
0229         qreal width = qMax<qreal>(0., size.width()-sizeHintRect.width());
0230 
0231         QTextCharFormat fmt = cursor.charFormat();
0232         if (flags & ScaleThumbnailFont) {
0233             //calculate the font reduction factor so that the text + the sizeHint fits
0234             qreal reductionFactor = qMin(width/documentSize.width(), size.height()/documentSize.height());
0235 
0236             fmt.setFontPointSize((int)(fmt.fontPointSize()*reductionFactor));
0237         }
0238 
0239         cursor.mergeCharFormat(fmt);
0240 
0241         frameCursor = FrameIterator(d->thumbnailHelperDocument->rootFrame());
0242         rootArea.setReferenceRect(0, width * 72.0 / qt_defaultDpiX(), 0, 1E6);
0243         rootArea.setNoWrap(1E6);
0244         rootArea.layoutRoot(&frameCursor);
0245         documentSize = rootArea.boundingRect().size();
0246         documentSize.setWidth(documentSize.width() * qt_defaultDpiX() / 72.0);
0247         documentSize.setHeight(documentSize.height() * qt_defaultDpiY() / 72.0);
0248         //center the preview in the pixmap
0249         qreal yOffset = (size.height()-documentSize.height())/2;
0250         p.save();
0251         if ((flags & CenterAlignThumbnail) && yOffset) {
0252             p.translate(0, yOffset);
0253         }
0254 
0255         p.scale(qt_defaultDpiX() / 72.0, qt_defaultDpiY() / 72.0);
0256 
0257         KoTextDocumentLayout::PaintContext pc;
0258         rootArea.paint(&p, pc);
0259 
0260         p.restore();
0261 
0262         p.setFont(sizeHintFont);
0263         p.drawText(QRectF(size.width()-sizeHintRect.width(), 0, sizeHintRect.width(),
0264                           size.height() /*because we want to be vertically centered in the pixmap, like the style name*/),Qt::AlignCenter, sizeHint);
0265     }
0266     else {
0267         //center the preview in the pixmap
0268         qreal yOffset = (size.height()-documentSize.height())/2;
0269         if ((flags & CenterAlignThumbnail) && yOffset) {
0270             p.translate(0, yOffset);
0271         }
0272 
0273         p.scale(qt_defaultDpiX() / 72.0, qt_defaultDpiY() / 72.0);
0274 
0275         KoTextDocumentLayout::PaintContext pc;
0276         rootArea.paint(&p, pc);
0277     }
0278 }
0279 
0280 void KoStyleThumbnailer::removeFromCache(KoParagraphStyle *style)
0281 {
0282     QString imageKey = "p_" + QString::number(reinterpret_cast<uintptr_t>(style)) + "_";
0283     removeFromCache(imageKey);
0284 }
0285 
0286 void KoStyleThumbnailer::removeFromCache(KoCharacterStyle *style)
0287 {
0288     QString imageKey = "c_" + QString::number(reinterpret_cast<uintptr_t>(style)) + "_";
0289     removeFromCache(imageKey);
0290 }
0291 
0292 void KoStyleThumbnailer::setText(const QString &text)
0293 {
0294     d->thumbnailText = text;
0295 }
0296 
0297 void KoStyleThumbnailer::removeFromCache(const QString &expr)
0298 {
0299     QList<QString> keys = d->thumbnailCache.keys();
0300     foreach (const QString &key, keys) {
0301         if (key.contains(expr)) {
0302             d->thumbnailCache.remove(key);
0303         }
0304     }
0305 }