KDEGames

kgamesvgdocument.cpp
1/*
2 SPDX-FileCopyrightText: 2007 Mark A. Taff <kde@marktaff.com>
3
4 SPDX-License-Identifier: LGPL-2.0-only
5*/
6
7#include "kgamesvgdocument.h"
8#include "kgamesvgdocument_p.h"
9
10// own
11#include <kdegamesprivate_logging.h>
12// KF
13#include <KCompressionDevice>
14// Qt
15#include <QBuffer>
16#include <QDomElement>
17#include <QDomNode>
18#include <QFile>
19#include <QRegularExpression>
20#include <QString>
21// Std
22#include <cmath>
23
24//
25// Public
26//
27
28/**
29 * @brief A class holding private members for KGameSvgDocument
30 *
31 * @see KGameSvgDocument
32 * @author Mark A. Taff <kde@marktaff.com>
33 * @version 0.1
34 */
35class KGameSvgDocumentPrivate
36{
37public:
38 /**
39 * @brief Instantiates a KGameSvgDocumentPrivate object
40 */
41 KGameSvgDocumentPrivate()
42 {
43 }
44
45 ~KGameSvgDocumentPrivate()
46 {
47 }
48
49 /**
50 * @brief Performs a preorder traversal of the DOM tree to find element matching @c attributeName & @c attributeValue
51 *
52 * @param attributeName The name of the attribute to find
53 * @param attributeValue The value of the @p attributeName attribute to find
54 * @param node The node to start the traversal from.
55 * @returns the node with id of @c elementId. If no node has that id, returns a null node.
56 */
57 QDomNode findElementById(const QString &attributeName, const QString &attributeValue, const QDomNode &node);
58
59 /**
60 * @brief Returns the current element
61 * @returns The current element
62 */
63 QDomElement currentElement() const;
64
65 /**
66 * @brief Sets the current element
67 *
68 * @returns nothing
69 */
70 void setCurrentElement();
71
72 /**
73 * @brief Returns whether the original style attribute has a trailing semicolon
74 * @returns whether the original style attribute has a trailing semicolon
75 */
76 bool styleHasTrailingSemicolon() const;
77
78 /**
79 * @brief Sets whether the original style attribute has a trailing semicolon
80 *
81 * @param hasSemicolon whether the original style attribute has a trailing semicolon
82 * @returns nothing
83 */
84 void setStyleHasTrailingSemicolon(bool hasSemicolon);
85
86 /**
87 * @brief The last node found by elementById, or a null node if not found.
88 */
89 QDomNode m_currentNode;
90
91 /**
92 * @brief The current node turned into an element.
93 */
94 QDomElement m_currentElement;
95
96 /**
97 * @brief The order Inkscape write properties in the style attribute of an element.
98 *
99 * Inkscape order is defined as:
100 * "fill", "fill-opacity", "fill-rule", "stroke", "stroke-width", "stroke-linecap",
101 * "stroke-linejoin", "stroke-miterlimit", "stroke-dasharray", "stroke-opacity"
102 */
103 QStringList m_inkscapeOrder;
104
105 /**
106 * @brief The xml that must be prepended to a node to make it a valid svg document
107 *
108 * Defined as: <?xml version="1.0" encoding="UTF-8" standalone="no"?><svg>
109 */
110 static const QString SVG_XML_PREPEND;
111
112 /**
113 * @brief The xml that must be appended to a node to make it a valid svg document
114 *
115 * Defined as: </svg>
116 */
117 static const QString SVG_XML_APPEND;
118
119 /**
120 * @brief The filename of the SVG file to open.
121 */
122 QString m_svgFilename;
123
124 /**
125 * @brief Whether the style attribute has a trailing semicolon
126 */
127 bool m_hasSemicolon;
128};
129
130const QString KGameSvgDocumentPrivate::SVG_XML_PREPEND = QStringLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><svg>");
131const QString KGameSvgDocumentPrivate::SVG_XML_APPEND = QStringLiteral("</svg>");
132
134 : QDomDocument()
135 , d(new KGameSvgDocumentPrivate)
136{
137}
138
140 : QDomDocument()
141 , d(new KGameSvgDocumentPrivate(*doc.d))
142{
143}
144
146
148{
150 *d = *doc.d;
151 return *this;
152}
153
155{
156 /* DOM is always "live", so there maybe a new root node. We always have to ask for the
157 * root node instead of keeping a pointer to it.
158 */
159 QDomElement docElem = documentElement();
160 QDomNode n = docElem.firstChild();
161
162 QDomNode node = d->findElementById(attributeName, attributeValue, n);
163 setCurrentNode(node);
164 return node;
165}
166
168{
169 return elementByUniqueAttributeValue(QStringLiteral("id"), attributeValue);
170}
171
173{
174 if (d->m_svgFilename.isEmpty()) {
175 qCDebug(KDEGAMESPRIVATE_LOG) << "KGameSvgDocument::load(): Filename not specified.";
176 return;
177 }
178
179 QFile file(d->m_svgFilename);
180 if (!file.open(QIODevice::ReadOnly)) {
181 return;
182 }
183 QByteArray content = file.readAll();
184
185 // If the file is compressed, decompress the contents before loading it.
186 if (!content.startsWith("<?xml")) // krazy:exclude=strings
187 {
188 QBuffer buf(&content);
189 KCompressionDevice flt(&buf, /*autoDeleteInputDevice*/ false, KCompressionDevice::GZip);
190 if (!flt.open(QIODevice::ReadOnly)) {
191 flt.close();
192 return;
193 }
194 QByteArray ar = flt.readAll();
195 flt.close();
196 content = ar;
197 }
198
199 if (!setContent(content)) {
200 file.close();
201 qCDebug(KDEGAMESPRIVATE_LOG) << "DOM content not set.";
202 return;
203 }
204 file.close();
205}
206
207void KGameSvgDocument::load(const QString &svgFilename)
208{
210 load();
211}
212
213void KGameSvgDocument::rotate(double degrees, MatrixOptions options)
214{
215 QTransform matrix;
216
217 if (options == ApplyToCurrentMatrix) {
218 matrix = transformMatrix().QTransform::rotate(degrees);
219 } else {
220 matrix = QTransform();
221 matrix.QTransform::rotate(degrees);
222 }
224}
225
226void KGameSvgDocument::translate(int xPixels, int yPixels, MatrixOptions options)
227{
228 QTransform matrix;
229
230 if (options == ApplyToCurrentMatrix) {
231 matrix = transformMatrix().QTransform::translate(xPixels, yPixels);
232 } else {
233 matrix = QTransform();
234 matrix.QTransform::translate(xPixels, yPixels);
235 }
237}
238
239void KGameSvgDocument::shear(double xRadians, double yRadians, MatrixOptions options)
240{
241 QTransform matrix;
242
243 if (options == ApplyToCurrentMatrix) {
244 matrix = transformMatrix().QTransform::shear(xRadians, yRadians);
245 } else {
246 matrix = QTransform();
247 matrix.QTransform::shear(xRadians, yRadians);
248 }
250}
251
252void KGameSvgDocument::skew(double xDegrees, double yDegrees, MatrixOptions options)
253{
254 double xRadians = xDegrees * (M_PI / 180);
255 double yRadians = yDegrees * (M_PI / 180);
256
257 shear(xRadians, yRadians, options);
258}
259
260void KGameSvgDocument::scale(double xFactor, double yFactor, MatrixOptions options)
261{
262 QTransform matrix;
263 if ((xFactor == 0) || (yFactor == 0)) {
264 qCWarning(KDEGAMESPRIVATE_LOG) << "KGameSvgDocument::scale: You cannot scale by zero";
265 }
266
267 if (options == ApplyToCurrentMatrix) {
268 matrix = transformMatrix().QTransform::scale(xFactor, yFactor);
269 } else {
270 matrix = QTransform();
271 matrix.QTransform::scale(xFactor, yFactor);
272 }
274}
275
277{
278 return d->m_currentNode;
279}
280
282{
283 d->m_currentNode = node;
284 d->setCurrentElement();
285}
286
288{
289 return d->m_svgFilename;
290}
291
293{
294 d->m_svgFilename = svgFilename;
295}
296
298{
299 return styleProperties().value(propertyName);
300}
301
302void KGameSvgDocument::setStyleProperty(const QString &propertyName, const QString &propertyValue)
303{
304 QHash<QString, QString> properties;
305
306 properties = styleProperties();
307 properties.insert(propertyName, propertyValue);
308
310}
311
313{
314 QString s, t, xml, defs, pattern;
315 QTextStream str(&s);
316 QTextStream str_t(&t);
317 QStringList defsAdded;
319
320 currentNode().save(str, 1);
321 xml = *str.string();
322
323 // Find and add any required gradients or patterns
324 pattern = QLatin1String("url") + WSP_ASTERISK + OPEN_PARENS + WSP_ASTERISK + QLatin1String("#(.*)") + WSP_ASTERISK + CLOSE_PARENS;
325 rx.setPattern(pattern);
326 if (rx.match(xml).hasMatch()) {
327 QDomNode node, nodeBase;
328 QString baseId;
329 QDomNode n = def();
330
332 while (i.hasNext()) {
333 QRegularExpressionMatch match = i.next();
334 const QString id = match.captured(1);
335 if (!defsAdded.contains(id)) {
336 node = d->findElementById(QStringLiteral("id"), id, n);
337 node.save(str_t, 1);
338 defsAdded.append(id);
339 }
340
341 // Find the gradient the above gradient is based on
342 baseId = node.toElement().attribute(QStringLiteral("xlink:href")).mid(1);
343 if (!defsAdded.contains(baseId)) {
344 nodeBase = d->findElementById(QStringLiteral("id"), baseId, n);
345 nodeBase.save(str_t, 1);
346 defsAdded.append(baseId);
347 }
348 }
349 defs = *str_t.string();
350 defs = QLatin1String("<defs>") + defs + QLatin1String("</defs>");
351 }
352
353 // Need to make node be a real svg document, so prepend and append required tags.
354 xml = d->SVG_XML_PREPEND + defs + xml + d->SVG_XML_APPEND;
355 return xml;
356}
357
362
364{
365 return d->m_currentElement.attribute(QStringLiteral("style"), QStringLiteral("Element has no style attribute."));
366}
367
368void KGameSvgDocument::setStyle(const QString &styleAttribute)
369{
370 d->m_currentElement.setAttribute(QStringLiteral("style"), styleAttribute);
371}
372
374{
375 return elementsByTagName(QStringLiteral("pattern"));
376}
377
379{
380 return elementsByTagName(QStringLiteral("linearGradient"));
381}
382
384{
385 return elementsByTagName(QStringLiteral("radialGradient"));
386}
387
389{
390 return elementsByTagName(QStringLiteral("defs"));
391}
392
394{
395 return defs().at(0);
396}
397
399{
400 return d->m_currentElement.attribute(QStringLiteral("transform"), QStringLiteral("Element has no transform attribute."));
401}
402
403void KGameSvgDocument::setTransform(const QString &transformAttribute)
404{
405 d->m_currentElement.setAttribute(QStringLiteral("transform"), transformAttribute);
406}
407
409{
410 QHash<QString, QString> stylePropertiesHash;
411
413
414 /* The style attr may have a trailing semi-colon. If it does, split()
415 * gives us an empty final element. Remove it or we get 'index out of range' errors
416 */
417 if (styleProperties.at((styleProperties.count() - 1)).isEmpty()) {
418 styleProperties.removeAt((styleProperties.count() - 1));
419 d->setStyleHasTrailingSemicolon(true);
420 } else {
421 d->setStyleHasTrailingSemicolon(false);
422 }
423
424 for (const QStringView &styleProperty : std::as_const(styleProperties)) {
425 const QList<QStringView> keyValuePair = styleProperty.split(QLatin1Char(':'));
426 stylePropertiesHash.insert(keyValuePair.at(0).toString(), keyValuePair.at(1).toString());
427 }
428 return stylePropertiesHash;
429}
430
432{
433 QHash<QString, QString> styleProperties = _styleProperties;
434 QString styleBuffer;
435
436 d->m_inkscapeOrder << QStringLiteral("fill") << QStringLiteral("fill-opacity") << QStringLiteral("fill-rule") << QStringLiteral("stroke")
437 << QStringLiteral("stroke-width") << QStringLiteral("stroke-linecap") << QStringLiteral("stroke-linejoin")
438 << QStringLiteral("stroke-miterlimit") << QStringLiteral("stroke-dasharray") << QStringLiteral("stroke-opacity");
439
440 if (options == UseInkscapeOrder) {
441 for (const QString &property : std::as_const(d->m_inkscapeOrder)) {
442 if (styleProperties.contains(property)) {
443 styleBuffer += property + QLatin1Char(':') + styleProperties.take(property) + QLatin1Char(';');
444 } else {
445 // Do Nothing
446 }
447 }
448 }
449
450 // Append any style properties
451 if (!styleProperties.isEmpty()) {
453 while (it.hasNext()) {
454 it.next();
455 styleBuffer += it.key() + QLatin1Char(':') + it.value() + QLatin1Char(';');
456 }
457 }
458
459 // Remove trailing semicolon if original didn't have one
460 if (!d->styleHasTrailingSemicolon()) {
461 styleBuffer.chop(1);
462 }
463 setStyle(styleBuffer);
464}
465
467{
468 /*
469 * Transform attributes can be quite complex. Here, we assemble this tangled web of
470 * complexity into an single matrix.
471 *
472 * The regex's that make this bearable live in kgamesvgdocument_p.h. As these regex's
473 * get quite complex, we have some code in tests/kgamesvgdocumenttest.cpp to help verify
474 * they are still correct after being edited.
475 *
476 * Warning: This code depends on the capturing parenthesis in the regex's not changing.
477 *
478 * For all the gory details, see http://www.w3.org/TR/SVG/coords.html#TransformAttribute
479 */
481 QString transformAttribute;
482 int i = 0;
483 QTransform baseMatrix = QTransform();
484
485 transformAttribute = transform();
486 if (transformAttribute == QLatin1String("Element has no transform attribute.")) {
487 return QTransform();
488 }
489 transformAttribute = transformAttribute.trimmed();
490
491 rx.setPattern(TRANSFORMS);
492 if (!rx.match(transformAttribute).hasMatch()) {
493 qCWarning(KDEGAMESPRIVATE_LOG) << "Transform attribute seems to be invalid. Check your SVG file.";
494 return QTransform();
495 }
496
497 rx.setPattern(TRANSFORM);
498
499 while (transformAttribute.size() > 0 && i < 32) // 32 is an arbitrary limit for the number of transforms for a single node
500 {
501 QRegularExpressionMatch match = rx.match(transformAttribute);
502 int result = match.capturedStart();
503 if (result != -1) // Found left-most transform
504 {
505 if (match.captured(1) == QLatin1String("matrix")) {
506 // If the first transform found is a matrix, use it as the base,
507 // else we use a null matrix.
508 if (i == 0) {
509 baseMatrix = QTransform(match.captured(2).toDouble(),
510 match.captured(3).toDouble(),
511 match.captured(4).toDouble(),
512 match.captured(5).toDouble(),
513 match.captured(6).toDouble(),
514 match.captured(7).toDouble());
515 } else {
516 baseMatrix = QTransform(match.captured(2).toDouble(),
517 match.captured(3).toDouble(),
518 match.captured(4).toDouble(),
519 match.captured(5).toDouble(),
520 match.captured(6).toDouble(),
521 match.captured(7).toDouble())
522 * baseMatrix;
523 }
524 }
525
526 if (match.captured(8) == QLatin1String("translate")) {
527 double x = match.captured(9).toDouble();
528 double y = match.captured(10).toDouble();
529 if (match.captured(10).isEmpty()) // y defaults to zero per SVG standard
530 {
531 y = 0;
532 }
533 baseMatrix = baseMatrix.translate(x, y);
534 }
535
536 if (match.captured(11) == QLatin1String("scale")) {
537 double x = match.captured(12).toDouble();
538 double y = match.captured(12).toDouble();
539 if (match.captured(13).isEmpty()) // y defaults to x per SVG standard
540 {
541 y = x;
542 }
543 baseMatrix = baseMatrix.scale(x, y);
544 }
545
546 if (match.captured(14) == QLatin1String("rotate")) {
547 double a = match.captured(15).toDouble();
548 double cx = match.captured(16).toDouble();
549 double cy = match.captured(17).toDouble();
550
551 if ((cx > 0) || (cy > 0)) // rotate around point (cx, cy)
552 {
553 baseMatrix.translate(cx, cy);
554 baseMatrix.rotate(a);
555 baseMatrix.translate((cx * -1), (cy * -1));
556 } else {
557 baseMatrix = baseMatrix.rotate(a); // rotate around origin
558 }
559 }
560
561 if (match.captured(18) == QLatin1String("skewX")) {
562 baseMatrix = baseMatrix.shear(match.captured(19).toDouble() * (M_PI / 180), 0);
563 }
564
565 if (match.captured(20) == QLatin1String("skewY")) {
566 baseMatrix = baseMatrix.shear(0, match.captured(21).toDouble() * (M_PI / 180));
567 }
568 }
569 transformAttribute = transformAttribute.mid(match.capturedLength() + result);
570 i++;
571 }
572
573 return baseMatrix;
574}
575
577{
578 QString transformBuffer, tmp;
579 QTransform null = QTransform();
580
581 if (options == ApplyToCurrentMatrix) {
582 matrix = transformMatrix() * matrix;
583 }
584
585 transformBuffer = QStringLiteral("matrix(");
586 transformBuffer += tmp.setNum(matrix.m11(), 'g', 7) + QLatin1Char(',');
587 transformBuffer += tmp.setNum(matrix.m12(), 'g', 7) + QLatin1Char(',');
588 transformBuffer += tmp.setNum(matrix.m21(), 'g', 7) + QLatin1Char(',');
589 transformBuffer += tmp.setNum(matrix.m22(), 'g', 7) + QLatin1Char(',');
590 transformBuffer += tmp.setNum(matrix.dx(), 'g', 7) + QLatin1Char(',');
591 transformBuffer += tmp.setNum(matrix.dy(), 'g', 7) + QLatin1Char(')');
592
593 if ((transform() == QLatin1String("Element has no transform attribute.")) && (matrix == null)) {
594 // Do not write a meaningless matrix to DOM
595 } else {
596 setTransform(transformBuffer);
597 }
598}
599
600//
601// Private
602//
603
604QDomNode KGameSvgDocumentPrivate::findElementById(const QString &attributeName, const QString &attributeValue, const QDomNode &node)
605{
606 QDomElement e = node.toElement(); // try to convert the node to an element.
607 QString value = e.attribute(attributeName, QStringLiteral("Element has no attribute with that name."));
608
609 if (value == attributeValue) {
610 // We found our node. Stop recursion and return it.
611 return node;
612 }
613
614 if (!node.firstChild().isNull()) {
615 QDomNode result = findElementById(attributeName, attributeValue, node.firstChild());
616 /** We have recursed, now we need to have this recursion end when
617 * the function call above returns
618 */
619 if (!result.isNull())
620 return result; // If we found the node with id, then return it
621 }
622 if (!node.nextSibling().isNull()) {
623 QDomNode result = findElementById(attributeName, attributeValue, node.nextSibling());
624 /** We have recursed, now we need to have this recursion end when
625 * the function call above returns */
626 if (!result.isNull())
627 return result;
628 }
629 if (!node.firstChild().isNull() && !node.nextSibling().isNull()) {
630 // Do Nothing
631 // qCDebug(KDEGAMESPRIVATE_LOG) << "No children or siblings.";
632 }
633
634 // Matching node not found, so return a null node.
635 return QDomNode();
636}
637
638QDomElement KGameSvgDocumentPrivate::currentElement() const
639{
640 return m_currentElement;
641}
642
643void KGameSvgDocumentPrivate::setCurrentElement()
644{
645 m_currentElement = m_currentNode.toElement();
646}
647
648bool KGameSvgDocumentPrivate::styleHasTrailingSemicolon() const
649{
650 return m_hasSemicolon;
651}
652
653void KGameSvgDocumentPrivate::setStyleHasTrailingSemicolon(bool hasSemicolon)
654{
655 m_hasSemicolon = hasSemicolon;
656}
void close() override
bool open(QIODevice::OpenMode mode) override
A class for manipulating an SVG file using DOM.
void translate(int xPixels, int yPixels, MatrixOptions options=ApplyToCurrentMatrix)
Moves the origin of the current node.
void setTransformMatrix(QTransform &matrix, MatrixOptions options=ApplyToCurrentMatrix)
Sets the transform attribute of the current node.
void scale(double xFactor, double yFactor, MatrixOptions options=ApplyToCurrentMatrix)
Scales the origin of the current node.
QDomNodeList linearGradients() const
Returns the linearGradients in the document.
void setTransform(const QString &transformAttribute)
Sets the transform attribute of the current node.
KGameSvgDocument & operator=(const KGameSvgDocument &doc)
Assignment Operator.
virtual ~KGameSvgDocument()
Destructor.
void load()
Reads the SVG file svgFilename() into DOM.
void rotate(double degrees, MatrixOptions options=ApplyToCurrentMatrix)
Rotates the origin of the current node counterclockwise.
void setStyleProperties(const QHash< QString, QString > &_styleProperties, const StylePropertySortOptions &options=Unsorted)
Sets the style properties of the current node.
void setSvgFilename(const QString &svgFilename)
Sets the current SVG filename.
QDomNode elementById(const QString &attributeValue)
Returns a node with the given id.
KGameSvgDocument()
Constructor.
void setStyleProperty(const QString &propertyName, const QString &propertyValue)
Sets the value of the style property given for the current node.
QString svgFilename() const
Returns the name of the SVG file this DOM represents.
QHash< QString, QString > styleProperties() const
Returns a hash of the style properties of the current node.
QDomNodeList defs() const
Returns the defs in the document.
QDomNode def() const
Returns the first def in the document.
QString nodeToSvg() const
Returns the current node and its children as a new xml svg document.
void shear(double xRadians, double yRadians, MatrixOptions options=ApplyToCurrentMatrix)
Shears the origin of the current node.
QString styleProperty(const QString &propertyName) const
Returns the value of the style property given for the current node.
@ ApplyToCurrentMatrix
Apply to current matrix.
@ ReplaceCurrentMatrix
Replace the current matrix.
QDomNodeList radialGradients() const
Returns the radialGradients in the document.
void setStyle(const QString &styleAttribute)
Sets the style attribute of the current node.
void setCurrentNode(const QDomNode &node)
Sets the current node.
QDomNodeList patterns() const
Returns the patterns in the document.
QString transform() const
Returns the transform attribute of the current node.
QTransform transformMatrix() const
Returns the transform attribute of the current node as a matrix.
QString style() const
Returns the style attribute of the current node.
QDomNode currentNode() const
Returns the last node found by elementById, or null if node not found.
void skew(double xDegrees, double yDegrees, MatrixOptions options=ApplyToCurrentMatrix)
Skews the origin of the current node.
QDomNode elementByUniqueAttributeValue(const QString &attributeName, const QString &attributeValue)
Returns the node with the given value for the given attribute.
QByteArray nodeToByteArray() const
Builds a new svg document and returns a QByteArray suitable for passing to QSvgRenderer::load().
@ UseInkscapeOrder
When building a style attribute, sort properties the same way Inkscape does.
bool startsWith(QByteArrayView bv) const const
QDomElement documentElement() const const
QDomNodeList elementsByTagName(const QString &tagname) const const
QDomDocument & operator=(const QDomDocument &x)
ParseResult setContent(QAnyStringView text, ParseOptions options)
QString attribute(const QString &name, const QString &defValue) const const
QDomNode firstChild() const const
bool isNull() const const
QDomNode nextSibling() const const
void save(QTextStream &stream, int indent, EncodingPolicy encodingPolicy) const const
QDomElement toElement() const const
QDomNode at(int index) const const
bool open(FILE *fh, OpenMode mode, FileHandleFlags handleFlags)
virtual void close() override
iterator insert(const Key &key, const T &value)
bool hasNext() const const
const Key & key() const const
const T & value() const const
QByteArray readAll()
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
QRegularExpressionMatchIterator globalMatch(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
void setPattern(const QString &pattern)
bool hasMatch() const const
QRegularExpressionMatch next()
void chop(qsizetype n)
QString mid(qsizetype position, qsizetype n) const const
QString & setNum(double n, char format, int precision)
qsizetype size() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QString trimmed() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QList< QStringView > split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
QString * string() const const
qreal dx() const const
qreal dy() const const
qreal m11() const const
qreal m12() const const
qreal m21() const const
qreal m22() const const
QTransform & rotate(qreal a, Qt::Axis axis)
QTransform & scale(qreal sx, qreal sy)
QTransform & shear(qreal sh, qreal sv)
QTransform & translate(qreal dx, qreal dy)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:13:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.