1 /* This file is part of the KDE project
2 * Copyright (C) 2006, 2009 Thomas Zander <zander@kde.org>
3 * Copyright (C) 2007 Sebastian Sauer <mail@dipe.org>
4 * Copyright (C) 2007 Pierre Ducroquet <pinaraf@gmail.com>
5 * Copyright (C) 2008 Girish Ramakrishnan <girish@forwardbias.in>
6 * Copyright (C) 2009,2011 KO GmbH <cbo@kogmbh.com>
7 * Copyright (C) 2011-2012 Pierre Stirnweiss <pstirnweiss@googlemail.com>
8 * Copyright (C) 2012 Gopalakrishna Bhat A <gopalakbhat@gmail.com>
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
19 *
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
24 */
25
26 #include "KoStyleThumbnailer.h"
27
28 #include "KoParagraphStyle.h"
29 #include "KoCharacterStyle.h"
30 #include "KoTextDocumentLayout.h"
31 #include "KoTextLayoutRootArea.h"
32 #include "FrameIterator.h"
33
34 #include <stdint.h>
35
36 #include <klocalizedstring.h>
37
38 #include <QCache>
39 #include <QFont>
40 #include <QImage>
41 #include <QPainter>
42 #include <QRect>
43 #include <QTextBlock>
44 #include <QTextCursor>
45 #include <QTextLength>
46
47 #include <TextLayoutDebug.h>
48
49 extern int qt_defaultDpiX();
50 extern int qt_defaultDpiY();
51
52 class Q_DECL_HIDDEN KoStyleThumbnailer::Private
53 {
54 public:
Private()55 Private() :
56 thumbnailHelperDocument(new QTextDocument),
57 documentLayout(new KoTextDocumentLayout(thumbnailHelperDocument)),
58 defaultSize(QSize(250, 48))
59 {
60 thumbnailHelperDocument->setDocumentLayout(documentLayout);
61 }
62
~Private()63 ~Private()
64 {
65 delete documentLayout;
66 delete thumbnailHelperDocument;
67 }
68
69 QTextDocument *thumbnailHelperDocument;
70 KoTextDocumentLayout *documentLayout;
71 QCache<QString, QImage> thumbnailCache; // cache of QImage representations of the styles
72 QSize defaultSize;
73 QString thumbnailText;
74 };
75
KoStyleThumbnailer()76 KoStyleThumbnailer::KoStyleThumbnailer()
77 : d(new Private())
78 {
79 }
80
~KoStyleThumbnailer()81 KoStyleThumbnailer::~KoStyleThumbnailer()
82 {
83 delete d;
84 }
85
thumbnail(KoParagraphStyle * style,const QSize & _size,bool recreateThumbnail,KoStyleThumbnailerFlags flags)86 QImage KoStyleThumbnailer::thumbnail(KoParagraphStyle *style, const QSize &_size, bool recreateThumbnail, KoStyleThumbnailerFlags flags)
87 {
88 if ((flags & UseStyleNameText) && (!style || style->name().isNull())) {
89 return QImage();
90 } else if ((! (flags & UseStyleNameText)) && d->thumbnailText.isEmpty()) {
91 return QImage();
92 }
93
94 const QSize &size = (!_size.isValid() || _size.isNull()) ? d->defaultSize : _size;
95
96 QString imageKey = "p_" + QString::number(reinterpret_cast<uintptr_t>(style)) + "_" + QString::number(size.width()) + "_" + QString::number(size.height());
97
98 if (!recreateThumbnail && d->thumbnailCache.object(imageKey)) {
99 return QImage(*(d->thumbnailCache.object(imageKey)));
100 }
101
102 QImage *im = new QImage(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
103 im->fill(QColor(Qt::transparent).rgba());
104
105 KoParagraphStyle *clone = style->clone();
106 //TODO: make the following real options
107 //we ignore these properties when the thumbnail would not be sufficient to preview properly the whole paragraph with margins.
108 clone->setMargin(QTextLength(QTextLength::FixedLength, 0));
109 clone->setPadding(0);
110 //
111 QTextCursor cursor(d->thumbnailHelperDocument);
112 cursor.select(QTextCursor::Document);
113 cursor.setBlockFormat(QTextBlockFormat());
114 cursor.setBlockCharFormat(QTextCharFormat());
115 cursor.setCharFormat(QTextCharFormat());
116 QTextBlock block = cursor.block();
117 clone->applyStyle(block, true);
118
119 QTextCharFormat format;
120 // Default to black as text color, to match what KoTextLayoutArea::paint(...)
121 // does, setting solid black if no brush is set. Otherwise the UI text color
122 // would be used, which might be too bright with dark UI color schemes
123 format.setForeground(QColor(Qt::black));
124 clone->KoCharacterStyle::applyStyle(format);
125 if (flags & UseStyleNameText) {
126 cursor.insertText(clone->name(), format);
127 } else {
128 cursor.insertText(d->thumbnailText, format);
129 }
130 layoutThumbnail(size, im, flags);
131 // Make a copy of the image before inserting in the cache
132 QImage res = QImage(*im);
133 // Because on inserting, QCache can decide to delete the object immediately
134 d->thumbnailCache.insert(imageKey, im);
135
136 delete clone;
137 return res;
138 }
139
thumbnail(KoCharacterStyle * characterStyle,KoParagraphStyle * paragraphStyle,const QSize & _size,bool recreateThumbnail,KoStyleThumbnailerFlags flags)140 QImage KoStyleThumbnailer::thumbnail(KoCharacterStyle *characterStyle, KoParagraphStyle *paragraphStyle, const QSize &_size, bool recreateThumbnail, KoStyleThumbnailerFlags flags)
141 {
142 if ((flags & UseStyleNameText) && (!characterStyle || characterStyle->name().isNull())) {
143 return QImage();
144 } else if ((! (flags & UseStyleNameText)) && d->thumbnailText.isEmpty()) {
145 return QImage();
146 }
147 else if (characterStyle == 0) {
148 return QImage();
149 }
150
151 const QSize &size = (!_size.isValid() || _size.isNull()) ? d->defaultSize : _size;
152
153 QString imageKey = "c_" + QString::number(reinterpret_cast<uintptr_t>(characterStyle)) + "_"
154 + "p_" + QString::number(reinterpret_cast<uintptr_t>(paragraphStyle)) + "_"
155 + QString::number(size.width()) + "_" + QString::number(size.height());
156
157 if (!recreateThumbnail && d->thumbnailCache.object(imageKey)) {
158 return QImage(*(d->thumbnailCache.object(imageKey)));
159 }
160
161 QImage *im = new QImage(size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
162 im->fill(QColor(Qt::transparent).rgba());
163
164 QTextCursor cursor(d->thumbnailHelperDocument);
165 QTextCharFormat format;
166 // Default to black as text color, to match what KoTextLayoutArea::paint(...)
167 // does, setting solid black if no brush is set. Otherwise the UI text color
168 // would be used, which might be too bright with dark UI color schemes
169 format.setForeground(QColor(Qt::black));
170 KoCharacterStyle *characterStyleClone = characterStyle->clone();
171 characterStyleClone->applyStyle(format);
172 cursor.select(QTextCursor::Document);
173 cursor.setBlockFormat(QTextBlockFormat());
174 cursor.setBlockCharFormat(QTextCharFormat());
175 cursor.setCharFormat(QTextCharFormat());
176
177 if (paragraphStyle) {
178 KoParagraphStyle *paragraphStyleClone = paragraphStyle->clone();
179 // paragraphStyleClone->KoCharacterStyle::applyStyle(format);
180 QTextBlock block = cursor.block();
181 paragraphStyleClone->applyStyle(block, true);
182 delete paragraphStyleClone;
183 paragraphStyleClone = 0;
184 }
185
186 if (flags & UseStyleNameText) {
187 cursor.insertText(characterStyleClone->name(), format);
188 } else {
189 cursor.insertText(d->thumbnailText, format);
190 }
191
192 layoutThumbnail(size, im, flags);
193 QImage res = QImage(*im);
194 d->thumbnailCache.insert(imageKey, im);
195 delete characterStyleClone;
196 return res;
197 }
198
setThumbnailSize(const QSize & size)199 void KoStyleThumbnailer::setThumbnailSize(const QSize &size)
200 {
201 d->defaultSize = size;
202 }
203
layoutThumbnail(const QSize & size,QImage * im,KoStyleThumbnailerFlags flags)204 void KoStyleThumbnailer::layoutThumbnail(const QSize &size, QImage *im, KoStyleThumbnailerFlags flags)
205 {
206 QPainter p(im);
207 d->documentLayout->removeRootArea();
208 KoTextLayoutRootArea rootArea(d->documentLayout);
209 rootArea.setReferenceRect(0, size.width() * 72.0 / qt_defaultDpiX(), 0, 1E6);
210 rootArea.setNoWrap(1E6);
211
212 FrameIterator frameCursor(d->thumbnailHelperDocument->rootFrame());
213 rootArea.layoutRoot(&frameCursor);
214
215 QSizeF documentSize = rootArea.boundingRect().size();
216 documentSize.setWidth(documentSize.width() * qt_defaultDpiX() / 72.0);
217 documentSize.setHeight(documentSize.height() * qt_defaultDpiY() / 72.0);
218 if (documentSize.width() > size.width() || documentSize.height() > size.height()) {
219 //calculate the space needed for the font size indicator (should the preview be too big with the style's font size
220 QTextCursor cursor(d->thumbnailHelperDocument);
221 cursor.select(QTextCursor::Document);
222 QString sizeHint = "\t" + QString::number(cursor.charFormat().fontPointSize()) + "pt";
223 p.save();
224 QFont sizeHintFont = p.font();
225 sizeHintFont.setPointSize(8);
226 p.setFont(sizeHintFont);
227 QRectF sizeHintRect(p.boundingRect(0, 0, 1, 1, Qt::AlignCenter, sizeHint));
228 p.restore();
229 qreal width = qMax<qreal>(0., size.width()-sizeHintRect.width());
230
231 QTextCharFormat fmt = cursor.charFormat();
232 if (flags & ScaleThumbnailFont) {
233 //calculate the font reduction factor so that the text + the sizeHint fits
234 qreal reductionFactor = qMin(width/documentSize.width(), size.height()/documentSize.height());
235
236 fmt.setFontPointSize((int)(fmt.fontPointSize()*reductionFactor));
237 }
238
239 cursor.mergeCharFormat(fmt);
240
241 frameCursor = FrameIterator(d->thumbnailHelperDocument->rootFrame());
242 rootArea.setReferenceRect(0, width * 72.0 / qt_defaultDpiX(), 0, 1E6);
243 rootArea.setNoWrap(1E6);
244 rootArea.layoutRoot(&frameCursor);
245 documentSize = rootArea.boundingRect().size();
246 documentSize.setWidth(documentSize.width() * qt_defaultDpiX() / 72.0);
247 documentSize.setHeight(documentSize.height() * qt_defaultDpiY() / 72.0);
248 //center the preview in the pixmap
249 qreal yOffset = (size.height()-documentSize.height())/2;
250 p.save();
251 if ((flags & CenterAlignThumbnail) && yOffset) {
252 p.translate(0, yOffset);
253 }
254
255 p.scale(qt_defaultDpiX() / 72.0, qt_defaultDpiY() / 72.0);
256
257 KoTextDocumentLayout::PaintContext pc;
258 rootArea.paint(&p, pc);
259
260 p.restore();
261
262 p.setFont(sizeHintFont);
263 p.drawText(QRectF(size.width()-sizeHintRect.width(), 0, sizeHintRect.width(),
264 size.height() /*because we want to be vertically centered in the pixmap, like the style name*/),Qt::AlignCenter, sizeHint);
265 }
266 else {
267 //center the preview in the pixmap
268 qreal yOffset = (size.height()-documentSize.height())/2;
269 if ((flags & CenterAlignThumbnail) && yOffset) {
270 p.translate(0, yOffset);
271 }
272
273 p.scale(qt_defaultDpiX() / 72.0, qt_defaultDpiY() / 72.0);
274
275 KoTextDocumentLayout::PaintContext pc;
276 rootArea.paint(&p, pc);
277 }
278 }
279
removeFromCache(KoParagraphStyle * style)280 void KoStyleThumbnailer::removeFromCache(KoParagraphStyle *style)
281 {
282 QString imageKey = "p_" + QString::number(reinterpret_cast<uintptr_t>(style)) + "_";
283 removeFromCache(imageKey);
284 }
285
removeFromCache(KoCharacterStyle * style)286 void KoStyleThumbnailer::removeFromCache(KoCharacterStyle *style)
287 {
288 QString imageKey = "c_" + QString::number(reinterpret_cast<uintptr_t>(style)) + "_";
289 removeFromCache(imageKey);
290 }
291
setText(const QString & text)292 void KoStyleThumbnailer::setText(const QString &text)
293 {
294 d->thumbnailText = text;
295 }
296
removeFromCache(const QString & expr)297 void KoStyleThumbnailer::removeFromCache(const QString &expr)
298 {
299 QList<QString> keys = d->thumbnailCache.keys();
300 foreach (const QString &key, keys) {
301 if (key.contains(expr)) {
302 d->thumbnailCache.remove(key);
303 }
304 }
305 }
306