KSvg

framesvg.cpp
1/*
2 SPDX-FileCopyrightText: 2008-2010 Aaron Seigo <aseigo@kde.org>
3 SPDX-FileCopyrightText: 2008-2010 Marco Martin <notmart@gmail.com>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "framesvg.h"
9#include "private/framesvg_p.h"
10
11#include <string>
12
13#include <QAtomicInt>
14#include <QBitmap>
15#include <QCryptographicHash>
16#include <QPainter>
17#include <QRegion>
18#include <QSizeF>
19#include <QStringBuilder>
20#include <QTimer>
21
22#include <QDebug>
23
24#include "debug_p.h"
25#include "imageset.h"
26#include "private/framesvg_helpers.h"
27#include "private/imageset_p.h"
28#include "private/svg_p.h"
29
30namespace KSvg
31{
33
34// Any attempt to generate a frame whose width or height is larger than this
35// will be rejected
36static const int MAX_FRAME_SIZE = 100000;
37
38FrameData::~FrameData()
39{
40 FrameSvgPrivate::s_sharedFrames[imageSet].remove(cacheId);
41}
42
43FrameSvg::FrameSvg(QObject *parent)
44 : Svg(parent)
45 , d(new FrameSvgPrivate(this))
46{
47 connect(this, &FrameSvg::repaintNeeded, this, std::bind(&FrameSvgPrivate::updateNeeded, d));
48}
49
50FrameSvg::~FrameSvg()
51{
52 delete d;
53}
54
56{
57 if (path == imagePath()) {
58 return;
59 }
60
61 clearCache();
62
64 Svg::d->setImagePath(path);
65 if (!d->repaintBlocked) {
66 d->updateFrameData(Svg::d->lastModified);
67 }
68}
69
71{
72 if (borders == d->enabledBorders) {
73 return;
74 }
75
76 d->enabledBorders = borders;
77
78 if (!d->repaintBlocked) {
79 d->updateFrameData(Svg::d->lastModified);
80 }
81}
82
83FrameSvg::EnabledBorders FrameSvg::enabledBorders() const
84{
85 return d->enabledBorders;
86}
87
89{
90 switch (location) {
91 case TopEdge:
92 setElementPrefix(QStringLiteral("north"));
93 break;
94 case BottomEdge:
95 setElementPrefix(QStringLiteral("south"));
96 break;
97 case LeftEdge:
98 setElementPrefix(QStringLiteral("west"));
99 break;
100 case RightEdge:
101 setElementPrefix(QStringLiteral("east"));
102 break;
103 default:
105 break;
106 }
107
108 d->location = location;
109}
110
112{
113 if (prefix.isEmpty() || !hasElement(prefix % QLatin1String("-center"))) {
114 d->prefix.clear();
115 } else {
116 d->prefix = prefix;
117 if (!d->prefix.isEmpty()) {
118 d->prefix += QLatin1Char('-');
119 }
120 }
121 d->requestedPrefix = prefix;
122
123 d->location = FrameSvg::Floating;
124
125 if (!d->repaintBlocked) {
126 d->updateFrameData(Svg::d->lastModified);
127 }
128}
129
130bool FrameSvg::hasElementPrefix(const QString &prefix) const
131{
132 // for now it simply checks if a center element exists,
133 // because it could make sense for certain themes to not have all the elements
134 if (prefix.isEmpty()) {
135 return hasElement(QStringLiteral("center"));
136 }
137 if (prefix.endsWith(QLatin1Char('-'))) {
138 return hasElement(prefix % QLatin1String("center"));
139 }
140
141 return hasElement(prefix % QLatin1String("-center"));
142}
143
145{
146 switch (location) {
147 case TopEdge:
148 return hasElementPrefix(QStringLiteral("north"));
149 case BottomEdge:
150 return hasElementPrefix(QStringLiteral("south"));
151 case LeftEdge:
152 return hasElementPrefix(QStringLiteral("west"));
153 case RightEdge:
154 return hasElementPrefix(QStringLiteral("east"));
155 default:
156 return hasElementPrefix(QString());
157 }
158}
159
161{
162 return d->requestedPrefix;
163}
164
166{
167 if (imagePath().isEmpty()) {
168 return;
169 }
170
171 if (size.isEmpty()) {
172#ifndef NDEBUG
173 // qCDebug(LOG_KSVG) << "Invalid size" << size;
174#endif
175 return;
176 }
177
178 if (d->frame && size.toSize() == d->frame->frameSize) {
179 return;
180 }
181 d->pendingFrameSize = size.toSize();
182
183 if (!d->repaintBlocked) {
184 d->updateFrameData(Svg::d->lastModified, FrameSvgPrivate::UpdateFrame);
185 }
186}
187
189{
190 if (!d->frame) {
191 return QSizeF(-1, -1);
192 } else {
193 return d->frameSize(d->frame.data());
194 }
195}
196
198{
199 if (!d->frame) {
200 return .0;
201 }
202
203 if (d->frame->noBorderPadding) {
204 return .0;
205 }
206
207 switch (edge) {
209 return d->frame->topMargin;
210
212 return d->frame->leftMargin;
213
215 return d->frame->rightMargin;
216
217 // KSvg::BottomMargin
218 default:
219 return d->frame->bottomMargin;
220 }
221}
222
224{
225 if (!d->frame) {
226 return .0;
227 }
228
229 if (d->frame->noBorderPadding) {
230 return .0;
231 }
232
233 switch (edge) {
235 return d->frame->insetTopMargin;
236
238 return d->frame->insetLeftMargin;
239
241 return d->frame->insetRightMargin;
242
243 // KSvg::BottomMargin
244 default:
245 return d->frame->insetBottomMargin;
246 }
247}
248
250{
251 if (!d->frame) {
252 return .0;
253 }
254
255 if (d->frame->noBorderPadding) {
256 return .0;
257 }
258
259 switch (edge) {
261 return d->frame->fixedTopMargin;
262
264 return d->frame->fixedLeftMargin;
265
267 return d->frame->fixedRightMargin;
268
269 // KSvg::BottomMargin
270 default:
271 return d->frame->fixedBottomMargin;
272 }
273}
274
275void FrameSvg::getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
276{
277 if (!d->frame || d->frame->noBorderPadding) {
278 left = top = right = bottom = 0;
279 return;
280 }
281
282 top = d->frame->topMargin;
283 left = d->frame->leftMargin;
284 right = d->frame->rightMargin;
285 bottom = d->frame->bottomMargin;
286}
287
288void FrameSvg::getFixedMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
289{
290 if (!d->frame || d->frame->noBorderPadding) {
291 left = top = right = bottom = 0;
292 return;
293 }
294
295 top = d->frame->fixedTopMargin;
296 left = d->frame->fixedLeftMargin;
297 right = d->frame->fixedRightMargin;
298 bottom = d->frame->fixedBottomMargin;
299}
300
301void FrameSvg::getInset(qreal &left, qreal &top, qreal &right, qreal &bottom) const
302{
303 if (!d->frame || d->frame->noBorderPadding) {
304 left = top = right = bottom = 0;
305 return;
306 }
307
308 top = d->frame->insetTopMargin;
309 left = d->frame->insetLeftMargin;
310 right = d->frame->insetRightMargin;
311 bottom = d->frame->insetBottomMargin;
312}
313
315{
316 if (d->frame) {
317 QRectF rect(QPoint(0, 0), d->frame->frameSize);
318 return rect.adjusted(d->frame->leftMargin, d->frame->topMargin, -d->frame->rightMargin, -d->frame->bottomMargin);
319 } else {
320 return QRectF();
321 }
322}
323
325{
326 // FIXME: the distinction between overlay and
327 return d->alphaMask();
328}
329
331{
332 QRegion result;
333 if (!d->frame) {
334 return result;
335 }
336
337 size_t id = qHash(d->cacheId(d->frame.data(), QString()), SvgRectsCache::s_seed);
338
339 QRegion *obj = d->frame->cachedMasks.object(id);
340
341 if (!obj) {
342 QPixmap alphaMask = d->alphaMask();
343 const qreal dpr = alphaMask.devicePixelRatio();
344
345 // region should always be in logical pixels, resize pixmap to be in the logical sizes
346 if (alphaMask.devicePixelRatio() != 1.0) {
348 }
349
350 // mask() of a QPixmap without alpha Channel will be null
351 // but if our mask has no lpha at all, we want instead consider the entire area as the mask
353 obj = new QRegion(QBitmap(alphaMask.mask()));
354 } else {
355 obj = new QRegion(alphaMask.rect());
356 }
357
358 result = *obj;
359 d->frame->cachedMasks.insert(id, obj);
360 } else {
361 result = *obj;
362 }
363 return result;
364}
365
367{
368 if (d->cacheAll && !cache) {
369 clearCache();
370 }
371
372 d->cacheAll = cache;
373}
374
376{
377 return d->cacheAll;
378}
379
381{
382 if (d->frame) {
383 d->frame->cachedBackground = QPixmap();
384 d->frame->cachedMasks.clear();
385 }
386 if (d->maskFrame) {
387 d->maskFrame->cachedBackground = QPixmap();
388 d->maskFrame->cachedMasks.clear();
389 }
390}
391
393{
394 if (d->frame->cachedBackground.isNull()) {
395 d->generateBackground(d->frame);
396 }
397
398 return d->frame->cachedBackground;
399}
400
401void FrameSvg::paintFrame(QPainter *painter, const QRectF &target, const QRectF &source)
402{
403 if (d->frame->cachedBackground.isNull()) {
404 d->generateBackground(d->frame);
405 if (d->frame->cachedBackground.isNull()) {
406 return;
407 }
408 }
409
410 painter->drawPixmap(target, d->frame->cachedBackground, source.isValid() ? source : target);
411}
412
413void FrameSvg::paintFrame(QPainter *painter, const QPointF &pos)
414{
415 if (d->frame->cachedBackground.isNull()) {
416 d->generateBackground(d->frame);
417 if (d->frame->cachedBackground.isNull()) {
418 return;
419 }
420 }
421
422 painter->drawPixmap(pos, d->frame->cachedBackground);
423}
424
426{
427 if (d->frame) {
428 return d->frame->fixedTopHeight + d->frame->fixedBottomHeight;
429 }
430 return 0;
431}
432
434{
435 if (d->frame) {
436 return d->frame->fixedRightWidth + d->frame->fixedLeftWidth;
437 }
438 return 0;
439}
440
441//#define DEBUG_FRAMESVG_CACHE
442FrameSvgPrivate::~FrameSvgPrivate() = default;
443
444QPixmap FrameSvgPrivate::alphaMask()
445{
446 QString maskPrefix;
447
448 if (q->hasElement(QLatin1String("mask-") % prefix % QLatin1String("center"))) {
449 maskPrefix = QStringLiteral("mask-");
450 }
451
452 if (maskPrefix.isNull()) {
453 if (frame->cachedBackground.isNull()) {
454 generateBackground(frame);
455 }
456 return frame->cachedBackground;
457 }
458
459 // We are setting the prefix only temporary to generate
460 // the needed mask image
461 const QString maskRequestedPrefix = requestedPrefix.isEmpty() ? QStringLiteral("mask") : maskPrefix % requestedPrefix;
462 maskPrefix = maskPrefix % prefix;
463
464 if (!maskFrame) {
465 maskFrame = lookupOrCreateMaskFrame(frame, maskPrefix, maskRequestedPrefix);
466 if (!maskFrame->cachedBackground.isNull()) {
467 return maskFrame->cachedBackground;
468 }
469 updateSizes(maskFrame);
470 generateBackground(maskFrame);
471 return maskFrame->cachedBackground;
472 }
473
474 const bool shouldUpdate = (maskFrame->enabledBorders != frame->enabledBorders //
475 || maskFrame->frameSize != frameSize(frame.data()) //
476 || maskFrame->imagePath != frame->imagePath);
477 if (shouldUpdate) {
478 maskFrame = lookupOrCreateMaskFrame(frame, maskPrefix, maskRequestedPrefix);
479 if (!maskFrame->cachedBackground.isNull()) {
480 return maskFrame->cachedBackground;
481 }
482 updateSizes(maskFrame);
483 }
484
485 if (maskFrame->cachedBackground.isNull()) {
486 generateBackground(maskFrame);
487 }
488
489 return maskFrame->cachedBackground;
490}
491
493FrameSvgPrivate::lookupOrCreateMaskFrame(const QSharedPointer<FrameData> &frame, const QString &maskPrefix, const QString &maskRequestedPrefix)
494{
495 const size_t key = qHash(cacheId(frame.data(), maskPrefix));
496 QSharedPointer<FrameData> mask = s_sharedFrames[q->imageSet()->d].value(key);
497
498 // See if we can find a suitable candidate in the shared frames.
499 // If there is one, use it.
500 if (mask) {
501 return mask;
502 }
503
504 mask.reset(new FrameData(*frame.data()));
505 mask->prefix = maskPrefix;
506 mask->requestedPrefix = maskRequestedPrefix;
507 mask->imageSet = q->imageSet()->d;
508 mask->imagePath = frame->imagePath;
509 mask->enabledBorders = frame->enabledBorders;
510 mask->frameSize = frameSize(frame).toSize();
511 mask->cacheId = key;
512 mask->lastModified = frame->lastModified;
513 s_sharedFrames[q->imageSet()->d].insert(key, mask);
514
515 return mask;
516}
517
518void FrameSvgPrivate::generateBackground(const QSharedPointer<FrameData> &frame)
519{
520 if (!frame->cachedBackground.isNull() || !q->hasElementPrefix(frame->prefix)) {
521 return;
522 }
523
524 const size_t id = qHash(cacheId(frame.data(), frame->prefix));
525
526 bool frameCached = !frame->cachedBackground.isNull();
527 bool overlayCached = false;
528
529 const bool overlayAvailable = !frame->prefix.startsWith(QLatin1String("mask-")) && q->hasElement(frame->prefix % QLatin1String("overlay"));
530 QPixmap overlay;
531 if (q->isUsingRenderingCache()) {
532 frameCached = q->imageSet()->d->findInCache(QString::number(id), frame->cachedBackground, frame->lastModified) && !frame->cachedBackground.isNull();
533 if (frameCached) {
534 frame->cachedBackground.setDevicePixelRatio(q->devicePixelRatio());
535 }
536
537 if (overlayAvailable) {
538 const size_t overlayId = qHash(cacheId(frame.data(), frame->prefix % QLatin1String("overlay")));
539 overlayCached = q->imageSet()->d->findInCache(QString::number(overlayId), overlay, frame->lastModified) && !overlay.isNull();
540 if (overlayCached) {
541 overlay.setDevicePixelRatio(q->devicePixelRatio());
542 }
543 }
544 }
545
546 if (!frameCached) {
547 generateFrameBackground(frame);
548 }
549
550 // Overlays
551 QSizeF overlaySize;
552 QPointF actualOverlayPos = QPointF(0, 0);
553 if (overlayAvailable && !overlayCached) {
554 overlaySize = q->elementSize(frame->prefix % QLatin1String("overlay")).toSize();
555
556 if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-pos-right"))) {
557 actualOverlayPos.setX(frame->frameSize.width() - overlaySize.width());
558 } else if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-pos-bottom"))) {
559 actualOverlayPos.setY(frame->frameSize.height() - overlaySize.height());
560 // Stretched or Tiled?
561 } else if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-stretch"))) {
562 overlaySize = frameSize(frame).toSize();
563 } else {
564 if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-horizontal"))) {
565 overlaySize.setWidth(frameSize(frame).width());
566 }
567 if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-vertical"))) {
568 overlaySize.setHeight(frameSize(frame).height());
569 }
570 }
571
572 overlay = alphaMask();
573 QPainter overlayPainter(&overlay);
574 overlayPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
575 // Tiling?
576 if (q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-horizontal"))
577 || q->hasElement(frame->prefix % QLatin1String("hint-overlay-tile-vertical"))) {
578 QSizeF s = q->size().toSize();
579 q->resize(q->elementSize(frame->prefix % QLatin1String("overlay")));
580
581 overlayPainter.drawTiledPixmap(QRectF(QPointF(0, 0), overlaySize), q->pixmap(frame->prefix % QLatin1String("overlay")));
582 q->resize(s);
583 } else {
584 q->paint(&overlayPainter, QRectF(actualOverlayPos, overlaySize), frame->prefix % QLatin1String("overlay"));
585 }
586
587 overlayPainter.end();
588 }
589
590 if (!frameCached) {
591 cacheFrame(frame->prefix, frame->cachedBackground, overlayCached ? overlay : QPixmap());
592 }
593
594 if (!overlay.isNull()) {
595 QPainter p(&frame->cachedBackground);
596 p.setCompositionMode(QPainter::CompositionMode_SourceOver);
597 p.drawPixmap(actualOverlayPos, overlay, QRectF(actualOverlayPos, overlaySize));
598 }
599}
600
601void FrameSvgPrivate::generateFrameBackground(const QSharedPointer<FrameData> &frame)
602{
603 // qCDebug(LOG_KSVG) << "generating background";
604 const QSizeF size = frameSize(frame) * q->devicePixelRatio();
605
606 if (!size.isValid()) {
607#ifndef NDEBUG
608 // qCDebug(LOG_KSVG) << "Invalid frame size" << size;
609#endif
610 return;
611 }
612 if (size.width() >= MAX_FRAME_SIZE || size.height() >= MAX_FRAME_SIZE) {
613 qCWarning(LOG_KSVG) << "Not generating frame background for a size whose width or height is more than" << MAX_FRAME_SIZE << size;
614 return;
615 }
616
617 // Don't cut away pieces of the frame
618 frame->cachedBackground = QPixmap(QSize(std::ceil(size.width()), std::ceil(size.height())));
619 frame->cachedBackground.fill(Qt::transparent);
620 QPainter p(&frame->cachedBackground);
621 p.setCompositionMode(QPainter::CompositionMode_Source);
622 p.setRenderHint(QPainter::SmoothPixmapTransform);
623
624 QRectF contentRect = contentGeometry(frame, size);
625 paintCenter(p, frame, contentRect, size);
626
627 paintCorner(p, frame, FrameSvg::LeftBorder | FrameSvg::TopBorder, contentRect);
628 paintCorner(p, frame, FrameSvg::RightBorder | FrameSvg::TopBorder, contentRect);
629 paintCorner(p, frame, FrameSvg::LeftBorder | FrameSvg::BottomBorder, contentRect);
630 paintCorner(p, frame, FrameSvg::RightBorder | FrameSvg::BottomBorder, contentRect);
631
632 // Sides
633 const qreal leftHeight = q->elementSize(frame->prefix % QLatin1String("left")).height();
634 paintBorder(p, frame, FrameSvg::LeftBorder, QSizeF(frame->leftWidth, leftHeight) * q->devicePixelRatio(), contentRect);
635 const qreal rightHeight = q->elementSize(frame->prefix % QLatin1String("right")).height();
636 paintBorder(p, frame, FrameSvg::RightBorder, QSizeF(frame->rightWidth, rightHeight) * q->devicePixelRatio(), contentRect);
637
638 const qreal topWidth = q->elementSize(frame->prefix % QLatin1String("top")).width();
639 paintBorder(p, frame, FrameSvg::TopBorder, QSizeF(topWidth, frame->topHeight) * q->devicePixelRatio(), contentRect);
640 const qreal bottomWidth = q->elementSize(frame->prefix % QLatin1String("bottom")).width();
641 paintBorder(p, frame, FrameSvg::BottomBorder, QSizeF(bottomWidth, frame->bottomHeight) * q->devicePixelRatio(), contentRect);
642 p.end();
643
644 // Set the devicePixelRatio only at the end, drawing all happened in device pixels
645 frame->cachedBackground.setDevicePixelRatio(q->devicePixelRatio());
646}
647
648QRectF FrameSvgPrivate::contentGeometry(const QSharedPointer<FrameData> &frame, const QSizeF &size) const
649{
650 const QSizeF contentSize(size.width() - frame->leftWidth * q->devicePixelRatio() - frame->rightWidth * q->devicePixelRatio(),
651 size.height() - frame->topHeight * q->devicePixelRatio() - frame->bottomHeight * q->devicePixelRatio());
652 QRectF contentRect(QPointF(0, 0), contentSize);
653 if (frame->enabledBorders & FrameSvg::LeftBorder && q->hasElement(frame->prefix % QLatin1String("left"))) {
654 contentRect.translate(frame->leftWidth * q->devicePixelRatio(), 0);
655 }
656
657 // Corners
658 if (frame->enabledBorders & FrameSvg::TopBorder && q->hasElement(frame->prefix % QLatin1String("top"))) {
659 contentRect.translate(0, frame->topHeight * q->devicePixelRatio());
660 }
661 return contentRect;
662}
663
664void FrameSvgPrivate::updateFrameData(uint lastModified, UpdateType updateType)
665{
666 auto fd = frame;
667 uint newKey = 0;
668
669 if (fd) {
670 const uint oldKey = fd->cacheId;
671
672 const QString oldPath = fd->imagePath;
673 const FrameSvg::EnabledBorders oldBorders = fd->enabledBorders;
674 const QSizeF currentSize = fd->frameSize;
675
676 fd->enabledBorders = enabledBorders;
677 fd->frameSize = pendingFrameSize;
678 fd->imagePath = q->imagePath();
679
680 newKey = qHash(cacheId(fd.data(), prefix));
681
682 // reset frame to old values
683 fd->enabledBorders = oldBorders;
684 fd->frameSize = currentSize;
685 fd->imagePath = oldPath;
686
687 // FIXME: something more efficient than string comparison?
688 if (oldKey == newKey) {
689 return;
690 }
691
692 // qCDebug(LOG_KSVG) << "looking for" << newKey;
693 auto newFd = FrameSvgPrivate::s_sharedFrames[q->imageSet()->d].value(newKey);
694 if (newFd) {
695 // qCDebug(LOG_KSVG) << "FOUND IT!" << newFd->refcount;
696 // we've found a match, use that one
697 Q_ASSERT(newKey == newFd.lock()->cacheId);
698 frame = newFd;
699 return;
700 }
701
702 fd.reset(new FrameData(*fd));
703 } else {
704 fd.reset(new FrameData(q, QString()));
705 }
706
707 frame = fd;
708 fd->prefix = prefix;
709 fd->requestedPrefix = requestedPrefix;
710 // updateSizes();
711 fd->enabledBorders = enabledBorders;
712 fd->frameSize = pendingFrameSize;
713 fd->imagePath = q->imagePath();
714 fd->lastModified = lastModified;
715 // was fd just created empty now?
716 if (newKey == 0) {
717 newKey = qHash(cacheId(fd.data(), prefix));
718 }
719
720 // we know it isn't in s_sharedFrames due to the check above, so insert it now
721 FrameSvgPrivate::s_sharedFrames[q->imageSet()->d].insert(newKey, fd);
722 fd->cacheId = newKey;
723 fd->imageSet = q->imageSet()->d;
724 if (updateType == UpdateFrameAndMargins) {
725 updateAndSignalSizes();
726 } else {
727 updateSizes(frame);
728 }
729}
730
731void FrameSvgPrivate::paintCenter(QPainter &p, const QSharedPointer<FrameData> &frame, const QRectF &contentRect, const QSizeF &fullSize)
732{
733 // fullSize and contentRect are in device pixels
734 if (!contentRect.isEmpty()) {
735 const QString centerElementId = frame->prefix % QLatin1String("center");
736 if (frame->tileCenter) {
737 QSizeF centerTileSize = q->elementSize(centerElementId);
738 QPixmap center(centerTileSize.toSize());
740
741 QPainter centerPainter(&center);
742 centerPainter.setCompositionMode(QPainter::CompositionMode_Source);
743 q->paint(&centerPainter, QRectF(QPointF(0, 0), centerTileSize), centerElementId);
744
745 if (frame->composeOverBorder) {
746 p.drawTiledPixmap(QRectF(QPointF(0, 0), fullSize), center);
747 } else {
748 p.drawTiledPixmap(FrameSvgHelpers::sectionRect(FrameSvg::NoBorder, contentRect, fullSize * q->devicePixelRatio()), center);
749 }
750 } else {
751 if (frame->composeOverBorder) {
752 q->paint(&p, QRectF(QPointF(0, 0), fullSize), centerElementId);
753 } else {
754 q->paint(&p, FrameSvgHelpers::sectionRect(FrameSvg::NoBorder, contentRect, fullSize * q->devicePixelRatio()), centerElementId);
755 }
756 }
757 }
758
759 if (frame->composeOverBorder) {
761 p.drawPixmap(QRectF(QPointF(0, 0), fullSize), alphaMask(), QRectF(QPointF(0, 0), alphaMask().size()));
763 }
764}
765
766void FrameSvgPrivate::paintBorder(QPainter &p,
767 const QSharedPointer<FrameData> &frame,
768 const FrameSvg::EnabledBorders borders,
769 const QSizeF &size,
770 const QRectF &contentRect) const
771{
772 // size and contentRect are in device pixels
773 QString side = frame->prefix % FrameSvgHelpers::borderToElementId(borders);
774 if (frame->enabledBorders & borders && q->hasElement(side) && !size.isEmpty()) {
775 if (frame->stretchBorders) {
776 q->paint(&p, FrameSvgHelpers::sectionRect(borders, contentRect, frame->frameSize * q->devicePixelRatio()), side);
777 } else {
778 QSize grownSize(std::ceil(size.width()), std::ceil(size.height()));
779 QPixmap px(grownSize);
780 // QPixmap px(QSize(std::ceil(size.width()), std::ceil(size.height())));
781 px.fill(Qt::transparent);
782
783 QPainter sidePainter(&px);
784 sidePainter.setCompositionMode(QPainter::CompositionMode_Source);
785 // A QRect as we have to exactly fill a QPixmap of integer size, prefer going slightly outside it to not have empty edges in the pixmap to tile
786 q->paint(&sidePainter, QRect(QPoint(0, 0), grownSize), side);
787
788 // We are composing QPixmaps here, so all objects with integer size
789 // Rounding the position and ceiling the size is the way that gives better tiled results
790 auto r = FrameSvgHelpers::sectionRect(borders, contentRect, frame->frameSize * q->devicePixelRatio());
791 r.setTopLeft(r.topLeft().toPoint());
792 r.setSize(QSizeF(std::ceil(r.size().width()), std::ceil(r.size().height())));
793
794 p.drawTiledPixmap(r, px);
795 }
796 }
797}
798
799void FrameSvgPrivate::paintCorner(QPainter &p, const QSharedPointer<FrameData> &frame, KSvg::FrameSvg::EnabledBorders border, const QRectF &contentRect) const
800{
801 // contentRect is in device pixels
802 // Draw the corner only if both borders in both directions are enabled.
803 if ((frame->enabledBorders & border) != border) {
804 return;
805 }
806 const QString corner = frame->prefix % FrameSvgHelpers::borderToElementId(border);
807 if (q->hasElement(corner)) {
808 auto r = FrameSvgHelpers::sectionRect(border, contentRect, frame->frameSize * q->devicePixelRatio());
809 // We are composing QPixmaps here, so all objects with integer size
810 // Rounding the position and ceiling the size is the way that gives better tiled results
811 r.setTopLeft(r.topLeft().toPoint());
812 r.setSize(QSizeF(std::ceil(r.size().width()), std::ceil(r.size().height())));
813 q->paint(&p, r.toRect(), corner);
814 }
815}
816
817SvgPrivate::CacheId FrameSvgPrivate::cacheId(FrameData *frame, const QString &prefixToSave) const
818{
819 const QSize size = frameSize(frame).toSize();
820 return SvgPrivate::CacheId{double(size.width()),
821 double(size.height()),
822 frame->imagePath,
823 prefixToSave,
824 q->status(),
825 q->devicePixelRatio(),
826 q->colorSet(),
827 (uint)frame->enabledBorders,
828 0,
829 q->Svg::d->lastModified};
830}
831
832void FrameSvgPrivate::cacheFrame(const QString &prefixToSave, const QPixmap &background, const QPixmap &overlay)
833{
834 if (!q->isUsingRenderingCache()) {
835 return;
836 }
837
838 // insert background
839 if (!frame) {
840 return;
841 }
842
843 const size_t id = qHash(cacheId(frame.data(), prefixToSave));
844
845 // qCDebug(LOG_KSVG)<<"Saving to cache frame"<<id;
846
847 q->imageSet()->d->insertIntoCache(QString::number(id), background, QString::number((qint64)q, 16) % prefixToSave);
848
849 if (!overlay.isNull()) {
850 // insert overlay
851 const size_t overlayId = qHash(cacheId(frame.data(), frame->prefix % QLatin1String("overlay")));
852 q->imageSet()->d->insertIntoCache(QString::number(overlayId), overlay, QString::number((qint64)q, 16) % prefixToSave % QLatin1String("overlay"));
853 }
854}
855
856void FrameSvgPrivate::updateSizes(FrameData *frame) const
857{
858 // qCDebug(LOG_KSVG) << "!!!!!!!!!!!!!!!!!!!!!! updating sizes" << prefix;
859 Q_ASSERT(frame);
860
861 QSizeF s = q->size();
862 q->resize();
863 if (!frame->cachedBackground.isNull()) {
864 frame->cachedBackground = QPixmap();
865 }
866
867 // This function needs to do a lot of string creation, since we have four
868 // sides with matching margins and insets. Rather than creating a new string
869 // every time for these, create a single buffer that can contain a full
870 // element name and pass that around using views, so we save a lot of
871 // allocations.
872 QString nameBuffer;
873 const auto offset = frame->prefix.length();
874 nameBuffer.reserve(offset + 30);
875 nameBuffer.append(frame->prefix);
876
877 // This uses UTF16 literals to avoid having to create QLatin1String and then
878 // converting that to a QString temporary for the replace operation.
879 // Additionally, we use a template parameter to provide us the compile-time
880 // length of the literal so we don't need to calculate that.
881 auto createName = [&nameBuffer, offset]<std::size_t length>(const char16_t(&name)[length]) {
882 nameBuffer.replace(offset, length - 1, reinterpret_cast<const QChar *>(name), length);
883 return QStringView(nameBuffer).mid(0, offset + length - 1);
884 };
885
886 // This has the same size regardless the border is enabled or not
887 frame->fixedTopHeight = q->elementSize(createName(u"top")).height();
888
889 if (auto topMargin = q->elementRect(createName(u"hint-top-margin")); topMargin.isValid()) {
890 frame->fixedTopMargin = topMargin.height();
891 } else {
892 frame->fixedTopMargin = frame->fixedTopHeight;
893 }
894
895 // The same, but its size depends from the margin being enabled
896 if (frame->enabledBorders & FrameSvg::TopBorder) {
897 frame->topMargin = frame->fixedTopMargin;
898 frame->topHeight = frame->fixedTopHeight;
899 } else {
900 frame->topMargin = frame->topHeight = 0;
901 }
902
903 if (auto topInset = q->elementRect(createName(u"hint-top-inset")); topInset.isValid()) {
904 frame->insetTopMargin = topInset.height();
905 } else {
906 frame->insetTopMargin = -1;
907 }
908
909 frame->fixedLeftWidth = q->elementSize(createName(u"left")).width();
910
911 if (auto leftMargin = q->elementRect(createName(u"hint-left-margin")); leftMargin.isValid()) {
912 frame->fixedLeftMargin = leftMargin.width();
913 } else {
914 frame->fixedLeftMargin = frame->fixedLeftWidth;
915 }
916
917 if (frame->enabledBorders & FrameSvg::LeftBorder) {
918 frame->leftMargin = frame->fixedLeftMargin;
919 frame->leftWidth = frame->fixedLeftWidth;
920 } else {
921 frame->leftMargin = frame->leftWidth = 0;
922 }
923
924 if (auto leftInset = q->elementRect(createName(u"hint-left-inset")); leftInset.isValid()) {
925 frame->insetLeftMargin = leftInset.width();
926 } else {
927 frame->insetLeftMargin = -1;
928 }
929
930 frame->fixedRightWidth = q->elementSize(createName(u"right")).width();
931
932 if (auto rightMargin = q->elementRect(createName(u"hint-right-margin")); rightMargin.isValid()) {
933 frame->fixedRightMargin = rightMargin.width();
934 } else {
935 frame->fixedRightMargin = frame->fixedRightWidth;
936 }
937
938 if (frame->enabledBorders & FrameSvg::RightBorder) {
939 frame->rightMargin = frame->fixedRightMargin;
940 frame->rightWidth = frame->fixedRightWidth;
941 } else {
942 frame->rightMargin = frame->rightWidth = 0;
943 }
944
945 if (auto rightInset = q->elementRect(createName(u"hint-right-inset")); rightInset.isValid()) {
946 frame->insetRightMargin = rightInset.width();
947 } else {
948 frame->insetRightMargin = -1;
949 }
950
951 frame->fixedBottomHeight = q->elementSize(createName(u"bottom")).height();
952
953 if (auto bottomMargin = q->elementRect(createName(u"hint-bottom-margin")); bottomMargin.isValid()) {
954 frame->fixedBottomMargin = bottomMargin.height();
955 } else {
956 frame->fixedBottomMargin = frame->fixedBottomHeight;
957 }
958
959 if (frame->enabledBorders & FrameSvg::BottomBorder) {
960 frame->bottomMargin = frame->fixedBottomMargin;
961 frame->bottomHeight = frame->fixedBottomHeight;
962 } else {
963 frame->bottomMargin = frame->bottomHeight = 0;
964 }
965
966 if (auto bottomInset = q->elementRect(createName(u"hint-bottom-inset")); bottomInset.isValid()) {
967 frame->insetBottomMargin = bottomInset.height();
968 } else {
969 frame->insetBottomMargin = -1;
970 }
971
972 static const QString maskPrefix = QStringLiteral("mask-");
973 static const QString hintTileCenter = QStringLiteral("hint-tile-center");
974 static const QString hintNoBorderPadding = QStringLiteral("hint-no-border-padding");
975 static const QString hintStretchBorders = QStringLiteral("hint-stretch-borders");
976
977 frame->composeOverBorder = (q->hasElement(createName(u"hint-compose-over-border")) && q->hasElement(maskPrefix % createName(u"center")));
978
979 // since it's rectangular, topWidth and bottomWidth must be the same
980 // the ones that don't have a frame->prefix is for retrocompatibility
981 frame->tileCenter = (q->hasElement(hintTileCenter) || q->hasElement(createName(u"hint-tile-center")));
982 frame->noBorderPadding = (q->hasElement(hintNoBorderPadding) || q->hasElement(createName(u"hint-no-border-padding")));
983 frame->stretchBorders = (q->hasElement(hintStretchBorders) || q->hasElement(createName(u"hint-stretch-borders")));
984 q->resize(s);
985}
986
987void FrameSvgPrivate::updateNeeded()
988{
989 q->setElementPrefix(requestedPrefix);
990 // frame not created yet?
991 if (!frame) {
992 return;
993 }
994 q->clearCache();
995 updateSizes(frame);
996}
997
998void FrameSvgPrivate::updateAndSignalSizes()
999{
1000 // frame not created yet?
1001 if (!frame) {
1002 return;
1003 }
1004 updateSizes(frame);
1005 Q_EMIT q->repaintNeeded();
1006}
1007
1008QSizeF FrameSvgPrivate::frameSize(FrameData *frame) const
1009{
1010 if (!frame) {
1011 return QSizeF();
1012 }
1013
1014 if (!frame->frameSize.isValid()) {
1015 updateSizes(frame);
1016 frame->frameSize = q->size().toSize();
1017 }
1018
1019 return frame->frameSize;
1020}
1021
1023{
1024 return d->prefix;
1025}
1026
1028{
1029 return d->repaintBlocked;
1030}
1031
1033{
1034 d->repaintBlocked = blocked;
1035
1036 if (!blocked) {
1037 d->updateFrameData(Svg::d->lastModified);
1038 }
1039}
1040
1041} // KSvg namespace
1042
1043#include "moc_framesvg.cpp"
Q_INVOKABLE bool hasElementPrefix(const QString &prefix) const
This method returns whether the SVG has the necessary elements with the given prefix to draw a frame.
Definition framesvg.cpp:130
Q_INVOKABLE QRectF contentsRect() const
This method returns the rectangle of the center element, taking the margins into account.
Definition framesvg.cpp:314
Q_INVOKABLE QSizeF frameSize() const
Definition framesvg.cpp:188
bool isRepaintBlocked() const
This method returns whether we are in a transaction of many changes at once.
Q_INVOKABLE void paintFrame(QPainter *painter, const QRectF &target, const QRectF &source=QRectF())
This method paints the loaded SVG with the elements that represents the border.
Definition framesvg.cpp:401
Q_INVOKABLE void clearCache()
This method deletes the internal cache.
Definition framesvg.cpp:380
@ RightMargin
The right margin.
Definition framesvg.h:95
@ LeftMargin
The left margin.
Definition framesvg.h:94
@ TopMargin
The top margin.
Definition framesvg.h:92
Q_INVOKABLE int minimumDrawingHeight()
This method returns the minimum height required to correctly draw this SVG.
Definition framesvg.cpp:425
Q_INVOKABLE void setCacheAllRenderedFrames(bool cache)
This method sets whether saving all the rendered prefixes in a cache or not.
Definition framesvg.cpp:366
Q_INVOKABLE int minimumDrawingWidth()
This method returns the minimum width required to correctly draw this SVG.
Definition framesvg.cpp:433
Q_INVOKABLE qreal marginSize(const FrameSvg::MarginEdge edge) const
This method returns the margin size for the given edge.
Definition framesvg.cpp:197
QString actualPrefix() const
This method returns the prefix that is actually being used (including a '-' at the end if not empty).
void setRepaintBlocked(bool blocked)
This method sets whether we should block rebuilding generated graphics for each change made.
Q_INVOKABLE QRegion mask() const
This method returns a mask that tightly contains the fully opaque areas of the SVG.
Definition framesvg.cpp:330
Q_INVOKABLE void getInset(qreal &left, qreal &top, qreal &right, qreal &bottom) const
This is a convenience method that extracts the size of the four inset margins and saves their size in...
Definition framesvg.cpp:301
void setEnabledBorders(const EnabledBorders borders)
This method sets which borders should be painted.
Definition framesvg.cpp:70
Q_INVOKABLE qreal insetSize(const FrameSvg::MarginEdge edge) const
This method returns the insets margin size for the specified edge.
Definition framesvg.cpp:223
Q_INVOKABLE void getMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
This is a convenience method that extracts the size of the four margins and saves their size into the...
Definition framesvg.cpp:275
@ LeftEdge
Along the left side of the screen.
Definition framesvg.h:86
@ Floating
Free floating.
Definition framesvg.h:83
@ RightEdge
Along the right side of the screen.
Definition framesvg.h:87
@ BottomEdge
Along the bottom of the screen.
Definition framesvg.h:85
@ TopEdge
Along the top of the screen.
Definition framesvg.h:84
Q_INVOKABLE QString prefix()
This method returns the prefix for SVG elements of the FrameSvg (including a '-' at the end if not em...
Definition framesvg.cpp:160
Q_INVOKABLE void setElementPrefix(KSvg::FrameSvg::LocationPrefix location)
This method sets the prefix (.
Definition framesvg.cpp:88
Q_INVOKABLE void setImagePath(const QString &path) override
Loads a new Svg.
Definition framesvg.cpp:55
Q_INVOKABLE QPixmap framePixmap()
This method returns a pixmap of the SVG represented by this object.
Definition framesvg.cpp:392
Q_INVOKABLE void getFixedMargins(qreal &left, qreal &top, qreal &right, qreal &bottom) const
This is a convenience method that extracts the size of the four margins and saves their size into the...
Definition framesvg.cpp:288
QPixmap alphaMask() const
This method returns a pixmap whose alpha channel is the opacity of the frame.
Definition framesvg.cpp:324
Q_INVOKABLE void resizeFrame(const QSizeF &size)
This method resizes the frame, maintaining the same border size.
Definition framesvg.cpp:165
Q_INVOKABLE qreal fixedMarginSize(const FrameSvg::MarginEdge edge) const
This method returns the margin size for the specified edge.
Definition framesvg.cpp:249
Q_INVOKABLE bool cacheAllRenderedFrames() const
This method returns whether all the different prefixes should be kept in a cache when rendered.
Definition framesvg.cpp:375
A theme aware image-centric SVG class.
Definition svg.h:46
void setContainsMultipleImages(bool multiple)
This method sets whether the SVG contains a single image or multiple ones.
Definition svg.cpp:1029
void repaintNeeded()
This signal is emitted whenever the SVG data has changed in such a way that a repaint is required.
Q_INVOKABLE bool hasElement(const QString &elementId) const
This method checks whether an element exists in the loaded SVG.
Definition svg.cpp:996
The KSvg namespace.
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
iterator insert(const Key &key, const T &value)
bool remove(const Key &key)
T value(const Key &key) const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
CompositionMode_SourceIn
SmoothPixmapTransform
void drawPixmap(const QPoint &point, const QPixmap &pixmap)
void drawTiledPixmap(const QRect &rectangle, const QPixmap &pixmap, const QPoint &position)
void setCompositionMode(CompositionMode mode)
qreal devicePixelRatio() const const
bool hasAlphaChannel() const const
int height() const const
bool isNull() const const
QBitmap mask() const const
QRect rect() const const
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
void setDevicePixelRatio(qreal scaleFactor)
int width() const const
void setX(qreal x)
void setY(qreal y)
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
bool isEmpty() const const
bool isValid() const const
void translate(const QPointF &offset)
T * data() const const
int height() const const
int width() const const
qreal height() const const
bool isEmpty() const const
bool isValid() const const
void setHeight(qreal height)
void setWidth(qreal width)
QSize toSize() const const
qreal width() const const
QString & append(QChar ch)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isNull() const const
qsizetype length() const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
QStringView mid(qsizetype start, qsizetype length) const const
transparent
QTextStream & center(QTextStream &stream)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:07:44 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.