Krita Source Code Documentation
Loading...
Searching...
No Matches
SvgUtil.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2009 Jan Hambrecht <jaham@gmx.net>
3 *
4 * SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7#include "SvgUtil.h"
8#include "SvgGraphicContext.h"
9
10#include <KoUnit.h>
11#include <KoSvgText.h>
12
13#include <QString>
14#include <QRectF>
15#include <QStringList>
16#include <QRegExp>
17
18#include <math.h>
19#include "kis_debug.h"
20#include "kis_global.h"
21
22#include <KoXmlWriter.h>
23#include "kis_dom_utils.h"
24
25#define DPI 72.0
26
27#define DEG2RAD(degree) degree/180.0*M_PI
28
30{
31 return value;
32}
33
35{
36 return value;
37}
38
40{
41 return value * gc->pixelsPerInch / DPI;
42}
43
44QPointF SvgUtil::toUserSpace(const QPointF &point)
45{
46 return QPointF(toUserSpace(point.x()), toUserSpace(point.y()));
47}
48
49QRectF SvgUtil::toUserSpace(const QRectF &rect)
50{
51 return QRectF(toUserSpace(rect.topLeft()), toUserSpace(rect.size()));
52}
53
54QSizeF SvgUtil::toUserSpace(const QSizeF &size)
55{
56 return QSizeF(toUserSpace(size.width()), toUserSpace(size.height()));
57}
58
60{
61 return KisDomUtils::toString(value * 100.0) + "%";
62}
63
64double SvgUtil::fromPercentage(QString s, bool *ok)
65{
66 if (s.endsWith('%'))
67 return KisDomUtils::toDouble(s.remove('%'), ok) / 100.0;
68 else
69 return KisDomUtils::toDouble(s, ok);
70}
71
72QPointF SvgUtil::objectToUserSpace(const QPointF &position, const QRectF &objectBound)
73{
74 qreal x = objectBound.left() + position.x() * objectBound.width();
75 qreal y = objectBound.top() + position.y() * objectBound.height();
76 return QPointF(x, y);
77}
78
79QSizeF SvgUtil::objectToUserSpace(const QSizeF &size, const QRectF &objectBound)
80{
81 qreal w = size.width() * objectBound.width();
82 qreal h = size.height() * objectBound.height();
83 return QSizeF(w, h);
84}
85
86QPointF SvgUtil::userSpaceToObject(const QPointF &position, const QRectF &objectBound)
87{
88 qreal x = 0.0;
89 if (objectBound.width() != 0)
90 x = (position.x() - objectBound.x()) / objectBound.width();
91 qreal y = 0.0;
92 if (objectBound.height() != 0)
93 y = (position.y() - objectBound.y()) / objectBound.height();
94 return QPointF(x, y);
95}
96
97QSizeF SvgUtil::userSpaceToObject(const QSizeF &size, const QRectF &objectBound)
98{
99 qreal w = objectBound.width() != 0 ? size.width() / objectBound.width() : 0.0;
100 qreal h = objectBound.height() != 0 ? size.height() / objectBound.height() : 0.0;
101 return QSizeF(w, h);
102}
103
104QString SvgUtil::transformToString(const QTransform &transform)
105{
106 if (transform.isIdentity())
107 return QString();
108
109 if (transform.type() == QTransform::TxTranslate) {
110 return QString("translate(%1, %2)")
111 .arg(KisDomUtils::toString(toUserSpace(transform.dx())))
112 .arg(KisDomUtils::toString(toUserSpace(transform.dy())));
113 } else {
114 return QString("matrix(%1 %2 %3 %4 %5 %6)")
115 .arg(KisDomUtils::toString(transform.m11()))
116 .arg(KisDomUtils::toString(transform.m12()))
117 .arg(KisDomUtils::toString(transform.m21()))
118 .arg(KisDomUtils::toString(transform.m22()))
119 .arg(KisDomUtils::toString(toUserSpace(transform.dx())))
120 .arg(KisDomUtils::toString(toUserSpace(transform.dy())));
121 }
122}
123
124void SvgUtil::writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter)
125{
126 const QString value = transformToString(transform);
127
128 if (!value.isEmpty()) {
129 shapeWriter.addAttribute(name.toLatin1().data(), value);
130 }
131}
132
133bool SvgUtil::parseViewBox(const QDomElement &e,
134 const QRectF &elementBounds,
135 QRectF *_viewRect, QTransform *_viewTransform)
136{
137 KIS_ASSERT(_viewRect);
138 KIS_ASSERT(_viewTransform);
139
140 QString viewBoxStr = e.attribute("viewBox");
141 if (viewBoxStr.isEmpty()) return false;
142
143 bool result = false;
144
145 QRectF viewBoxRect;
146 // this is a workaround for bug 260429 for a file generated by blender
147 // who has px in the viewbox which is wrong.
148 // reported as bug https://developer.blender.org/T30971
149 viewBoxStr.remove("px");
150
151 QStringList points = viewBoxStr.replace(',', ' ').simplified().split(' ');
152 if (points.count() == 4) {
153 viewBoxRect.setX(SvgUtil::fromUserSpace(points[0].toDouble()));
154 viewBoxRect.setY(SvgUtil::fromUserSpace(points[1].toDouble()));
155 viewBoxRect.setWidth(SvgUtil::fromUserSpace(points[2].toDouble()));
156 viewBoxRect.setHeight(SvgUtil::fromUserSpace(points[3].toDouble()));
157
158 result = true;
159 } else {
160 // TODO: WARNING!
161 }
162
163 if (!result) return false;
164
165 qreal scaleX = 1;
166 if (!qFuzzyCompare(elementBounds.width(), viewBoxRect.width())) {
167 scaleX = elementBounds.width() / viewBoxRect.width();
168 }
169 qreal scaleY = 1;
170 if (!qFuzzyCompare(elementBounds.height(), viewBoxRect.height())) {
171 scaleY = elementBounds.height() / viewBoxRect.height();
172 }
173
174 QTransform viewBoxTransform =
175 QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) *
176 QTransform::fromScale(scaleX, scaleY) *
177 QTransform::fromTranslate(elementBounds.x(), elementBounds.y());
178
179 const QString aspectString = e.attribute("preserveAspectRatio");
180 // give initial value if value not defined
181 PreserveAspectRatioParser p( (!aspectString.isEmpty())? aspectString : QString("xMidYMid meet"));
182 parseAspectRatio(p, elementBounds, viewBoxRect, &viewBoxTransform);
183
184 *_viewRect = viewBoxRect;
185 *_viewTransform = viewBoxTransform;
186
187 return result;
188}
189
190void SvgUtil::parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewBoxRect, QTransform *_viewTransform)
191{
192 if (p.mode != Qt::IgnoreAspectRatio) {
193 QTransform viewBoxTransform = *_viewTransform;
194
195 const qreal tan1 = viewBoxRect.height() / viewBoxRect.width();
196 const qreal tan2 = elementBounds.height() / elementBounds.width();
197
198 const qreal uniformScale =
199 (p.mode == Qt::KeepAspectRatioByExpanding) ^ (tan1 > tan2) ?
200 elementBounds.height() / viewBoxRect.height() :
201 elementBounds.width() / viewBoxRect.width();
202
203 viewBoxTransform =
204 QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) *
205 QTransform::fromScale(uniformScale, uniformScale) *
206 QTransform::fromTranslate(elementBounds.x(), elementBounds.y());
207
208 const QPointF viewBoxAnchor = viewBoxTransform.map(p.rectAnchorPoint(viewBoxRect));
209 const QPointF elementAnchor = p.rectAnchorPoint(elementBounds);
210 const QPointF offset = elementAnchor - viewBoxAnchor;
211
212 viewBoxTransform = viewBoxTransform * QTransform::fromTranslate(offset.x(), offset.y());
213
214 *_viewTransform = viewBoxTransform;
215 }
216}
217
218qreal SvgUtil::parseUnit(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, QStringView unit, bool horiz, bool vert, const QRectF &bbox)
219{
220 if (unit.isEmpty())
221 return 0.0;
222 QByteArray unitLatin1 = unit.toLatin1();
223 // TODO : percentage?
224 const char *start = unitLatin1.data();
225 if (!start) {
226 return 0.0;
227 }
228 KoSvgText::CssLengthPercentage length = parseUnitStruct(gc, unit, horiz, vert, bbox);
229 length.convertToAbsolute(resolved.metrics(), resolved.fontSize().value);
230
231 return length.value;
232}
233
234KoSvgText::CssLengthPercentage SvgUtil::parseUnitStruct(SvgGraphicsContext *gc, QStringView unit, bool horiz, bool vert, const QRectF &bbox)
235{
236 return parseUnitStructImpl(gc, unit, horiz, vert, bbox, true);
237}
238
240{
241 return parseUnitStructImpl(gc, unit, false, false, QRectF(), false);
242}
243
244KoSvgText::CssLengthPercentage SvgUtil::parseUnitStructImpl(SvgGraphicsContext *gc, QStringView unit, bool horiz, bool vert, const QRectF &bbox, bool percentageViewBox)
245{
247
248 if (unit.isEmpty())
249 return length;
250 QByteArray unitLatin1 = unit.trimmed().toLatin1();
251 // TODO : percentage?
252 const char *start = unitLatin1.data();
253 if (!start) {
254 return length;
255 }
256 const char *end = parseNumber(start, length.value);
257
258 if (int(end - start) < unit.length()) {
259 if (unit.right(2) == QLatin1String("px"))
260 length.value = SvgUtil::fromUserSpace(length.value);
261 else if (unit.right(2) == QLatin1String("pt"))
262 length.value = ptToPx(gc, length.value);
263 else if (unit.right(2) == QLatin1String("cm"))
264 length.value = ptToPx(gc, CM_TO_POINT(length.value));
265 else if (unit.right(2) == QLatin1String("pc"))
266 length.value = ptToPx(gc, PI_TO_POINT(length.value));
267 else if (unit.right(2) == QLatin1String("mm"))
268 length.value = ptToPx(gc, MM_TO_POINT(length.value));
269 else if (unit.right(2) == QLatin1String("in"))
270 length.value = ptToPx(gc, INCH_TO_POINT(length.value));
271 else if (unit.right(2) == QLatin1String("em")) {
273 } else if (unit.right(2) == QLatin1String("ex")) {
275 } else if (unit.right(3) == QLatin1String("cap")) {
277 } else if (unit.right(2) == QLatin1String("ch")) {
279 } else if (unit.right(2) == QLatin1String("ic")) {
281 } else if (unit.right(2) == QLatin1String("lh")) {
283 } else if (unit.right(1) == QLatin1Char('%')) {
284
285 if (percentageViewBox) {
286 if (horiz && vert)
287 length.value = (length.value / 100.0) * (sqrt(pow(bbox.width(), 2) + pow(bbox.height(), 2)) / sqrt(2.0));
288 else if (horiz)
289 length.value = (length.value / 100.0) * bbox.width();
290 else if (vert)
291 length.value = (length.value / 100.0) * bbox.height();
292 } else {
293 length.value = (length.value / 100.0);
295 }
296 }
297 } else {
298 length.value = SvgUtil::fromUserSpace(length.value);
299 }
300
301 return length;
302}
303
304qreal SvgUtil::parseUnitX(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
305{
306 if (gc->forcePercentage) {
307 return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.width();
308 } else {
309 return SvgUtil::parseUnit(gc, resolved, unit, true, false, gc->currentBoundingBox);
310 }
311}
312
313qreal SvgUtil::parseUnitY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
314{
315 if (gc->forcePercentage) {
316 return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.height();
317 } else {
318 return SvgUtil::parseUnit(gc, resolved, unit, false, true, gc->currentBoundingBox);
319 }
320}
321
322qreal SvgUtil::parseUnitXY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
323{
324 if (gc->forcePercentage) {
325 const qreal value = SvgUtil::fromPercentage(unit);
326 return value * sqrt(pow(gc->currentBoundingBox.width(), 2) + pow(gc->currentBoundingBox.height(), 2)) / sqrt(2.0);
327 } else {
328 return SvgUtil::parseUnit(gc, resolved, unit, true, true, gc->currentBoundingBox);
329 }
330}
331
332qreal SvgUtil::parseUnitAngular(SvgGraphicsContext *gc, const QString &unit)
333{
334 Q_UNUSED(gc);
335
336 qreal value = 0.0;
337
338 if (unit.isEmpty()) return value;
339 QByteArray unitLatin1 = unit.toLower().toLatin1();
340
341 const char *start = unitLatin1.data();
342 if (!start) return value;
343
344 const char *end = parseNumber(start, value);
345
346 if (int(end - start) < unit.length()) {
347 if (unit.right(3) == "deg") {
349 } else if (unit.right(4) == "grad") {
350 value *= M_PI / 200;
351 } else if (unit.right(3) == "rad") {
352 // noop!
353 } else {
355 }
356 } else {
358 }
359
360 return value;
361}
362
363qreal SvgUtil::parseNumber(const QString &string)
364{
365 qreal value = 0.0;
366
367 if (string.isEmpty()) return value;
368 QByteArray unitLatin1 = string.toLatin1();
369
370 const char *start = unitLatin1.data();
371 if (!start) return value;
372
373 const char *end = parseNumber(start, value);
374 KIS_SAFE_ASSERT_RECOVER_NOOP(int(end - start) == string.length());
375 return value;
376}
377
378const char * SvgUtil::parseNumber(const char *ptr, qreal &number)
379{
380 int integer, exponent;
381 qreal decimal, frac;
382 int sign, expsign;
383
384 exponent = 0;
385 integer = 0;
386 frac = 1.0;
387 decimal = 0;
388 sign = 1;
389 expsign = 1;
390
391 // read the sign
392 if (*ptr == '+') {
393 ptr++;
394 } else if (*ptr == '-') {
395 ptr++;
396 sign = -1;
397 }
398
399 // read the integer part
400 while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
401 integer = (integer * 10) + *(ptr++) - '0';
402 if (*ptr == '.') { // read the decimals
403 ptr++;
404 while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9')
405 decimal += (*(ptr++) - '0') * (frac *= 0.1);
406 }
407
408 if (*ptr == 'e' || *ptr == 'E') { // read the exponent part
409 ptr++;
410
411 // read the sign of the exponent
412 if (*ptr == '+') {
413 ptr++;
414 } else if (*ptr == '-') {
415 ptr++;
416 expsign = -1;
417 }
418
419 exponent = 0;
420 while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') {
421 exponent *= 10;
422 exponent += *ptr - '0';
423 ptr++;
424 }
425 }
426 number = integer + decimal;
427 number *= sign * pow((double)10, double(expsign * exponent));
428
429 return ptr;
430}
431
432QString SvgUtil::mapExtendedShapeTag(const QString &tagName, const QDomElement &element)
433{
434 QString result = tagName;
435
436 if (tagName == "path") {
437 QString kritaType = element.attribute("krita:type", "");
438 QString sodipodiType = element.attribute("sodipodi:type", "");
439
440 if (kritaType == "arc") {
441 result = "krita:arc";
442 } else if (sodipodiType == "arc") {
443 result = "sodipodi:arc";
444 }
445 }
446
447 return result;
448}
449
451{
452 QString attribute = str;
453 attribute.replace(',', ' ');
454 attribute.remove('\r');
455 attribute.remove('\n');
456 return attribute.simplified().split(' ', Qt::SkipEmptyParts);
457}
458
460{
461 QRegExp rexp("(defer)?\\s*(none|(x(Min|Max|Mid)Y(Min|Max|Mid)))\\s*(meet|slice)?", Qt::CaseInsensitive);
462 int index = rexp.indexIn(str.toLower());
463
464 if (index >= 0) {
465 if (rexp.cap(1) == "defer") {
466 defer = true;
467 }
468
469 if (rexp.cap(2) != "none") {
470 xAlignment = alignmentFromString(rexp.cap(4));
471 yAlignment = alignmentFromString(rexp.cap(5));
472 mode = rexp.cap(6) == "slice" ?
473 Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio;
474 }
475 }
476}
477
479{
480 return QPointF(alignedValue(rc.x(), rc.x() + rc.width(), xAlignment),
481 alignedValue(rc.y(), rc.y() + rc.height(), yAlignment));
482}
483
485{
486 QString result;
487
488 if (!defer &&
489 xAlignment == Middle &&
490 yAlignment == Middle &&
491 mode == Qt::KeepAspectRatio) {
492
493 return result;
494 }
495
496 if (defer) {
497 result += "defer ";
498 }
499
500 if (mode == Qt::IgnoreAspectRatio) {
501 result += "none";
502 } else {
503 result += QString("x%1Y%2")
504 .arg(alignmentToString(xAlignment))
505 .arg(alignmentToString(yAlignment));
506
507 if (mode == Qt::KeepAspectRatioByExpanding) {
508 result += " slice";
509 }
510 }
511
512 return result;
513}
514
516 return
517 str == "max" ? Max :
518 str == "mid" ? Middle : Min;
519}
520
522{
523 return
524 alignment == Max ? "Max" :
525 alignment == Min ? "Min" :
526 "Mid";
527
528}
529
531{
532 qreal result = min;
533
534 switch (alignment) {
535 case Min:
536 result = min;
537 break;
538 case Middle:
539 result = 0.5 * (min + max);
540 break;
541 case Max:
542 result = max;
543 break;
544 }
545
546 return result;
547}
qreal length(const QPointF &vec)
Definition Ellipse.cc:82
float value(const T *src, size_t ch)
const Params2D p
constexpr qreal CM_TO_POINT(qreal cm)
Definition KoUnit.h:34
constexpr qreal INCH_TO_POINT(qreal inch)
Definition KoUnit.h:38
constexpr qreal PI_TO_POINT(qreal pi)
Definition KoUnit.h:43
constexpr qreal MM_TO_POINT(qreal mm)
Definition KoUnit.h:32
#define DPI
Definition SvgUtil.cpp:25
KoSvgText::FontMetrics metrics(const bool withResolvedLineHeight=true) const
metrics Return the metrics of the first available font.
KoSvgText::CssLengthPercentage fontSize() const
void addAttribute(const char *attrName, const QString &value)
Definition KoXmlWriter.h:61
QRectF currentBoundingBox
the current bound box used for bounding box units
qreal pixelsPerInch
controls the resolution of the image raster
bool forcePercentage
force parsing coordinates/length as percentages of currentBoundbox
static double toUserSpace(double value)
Definition SvgUtil.cpp:34
static qreal parseUnitX(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in x-direction
Definition SvgUtil.cpp:304
static double fromPercentage(QString s, bool *ok=nullptr)
Definition SvgUtil.cpp:64
static QString toPercentage(qreal value)
Definition SvgUtil.cpp:59
static qreal parseUnitAngular(SvgGraphicsContext *gc, const QString &unit)
parses angle, result in radians!
Definition SvgUtil.cpp:332
static bool parseViewBox(const QDomElement &e, const QRectF &elementBounds, QRectF *_viewRect, QTransform *_viewTransform)
Parses a viewbox attribute into an rectangle.
Definition SvgUtil.cpp:133
static const char * parseNumber(const char *ptr, qreal &number)
parses the number into parameter number
Definition SvgUtil.cpp:378
static QStringList simplifyList(const QString &str)
Definition SvgUtil.cpp:450
static QPointF objectToUserSpace(const QPointF &position, const QRectF &objectBound)
Definition SvgUtil.cpp:72
static QString transformToString(const QTransform &transform)
Converts specified transformation to a string.
Definition SvgUtil.cpp:104
static KoSvgText::CssLengthPercentage parseUnitStructImpl(SvgGraphicsContext *gc, QStringView, bool horiz=false, bool vert=false, const QRectF &bbox=QRectF(), bool percentageViewBox=false)
Parse length attribute into struct.
Definition SvgUtil.cpp:244
static double ptToPx(SvgGraphicsContext *gc, double value)
Definition SvgUtil.cpp:39
static KoSvgText::CssLengthPercentage parseTextUnitStruct(SvgGraphicsContext *gc, QStringView unit)
Unit structs for text do not need the percentage to be resolved to viewport in most cases.
Definition SvgUtil.cpp:239
static qreal parseUnit(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, QStringView, bool horiz=false, bool vert=false, const QRectF &bbox=QRectF())
Parses a length attribute.
Definition SvgUtil.cpp:218
static void parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewRect, QTransform *_viewTransform)
Definition SvgUtil.cpp:190
static double fromUserSpace(double value)
Definition SvgUtil.cpp:29
static QPointF userSpaceToObject(const QPointF &position, const QRectF &objectBound)
Definition SvgUtil.cpp:86
static qreal parseUnitXY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in xy-direction
Definition SvgUtil.cpp:322
static KoSvgText::CssLengthPercentage parseUnitStruct(SvgGraphicsContext *gc, QStringView unit, bool horiz=false, bool vert=false, const QRectF &bbox=QRectF())
Parse length attribute into a struct, always resolving the percentage to viewport.
Definition SvgUtil.cpp:234
static void writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter)
Writes a transform as an attribute name iff the transform is not empty.
Definition SvgUtil.cpp:124
static qreal parseUnitY(SvgGraphicsContext *gc, const KoSvgTextProperties &resolved, const QString &unit)
parses a length attribute in y-direction
Definition SvgUtil.cpp:313
static QString mapExtendedShapeTag(const QString &tagName, const QDomElement &element)
Definition SvgUtil.cpp:432
static bool qFuzzyCompare(half p1, half p2)
#define KIS_SAFE_ASSERT_RECOVER_NOOP(cond)
Definition kis_assert.h:130
#define KIS_ASSERT(cond)
Definition kis_assert.h:33
T kisDegreesToRadians(T degrees)
Definition kis_global.h:176
#define M_PI
Definition kis_global.h:111
double toDouble(const quint8 *data, int channelpos)
double toDouble(const QString &str, bool *ok=nullptr)
QString toString(const QString &value)
@ Cap
multiply by font-x-height.
Definition KoSvgText.h:412
@ Ch
multiply by font cap height
Definition KoSvgText.h:413
@ Lh
multiply by width of "U+6C34", represents average full width script advance.
Definition KoSvgText.h:415
@ Ex
multiply by Font-size
Definition KoSvgText.h:411
@ Ic
multiply by width of "0", represents average proportional script advance.
Definition KoSvgText.h:414
static qreal alignedValue(qreal min, qreal max, Alignment alignment)
Definition SvgUtil.cpp:530
Alignment alignmentFromString(const QString &str) const
Definition SvgUtil.cpp:515
QPointF rectAnchorPoint(const QRectF &rc) const
Definition SvgUtil.cpp:478
PreserveAspectRatioParser(const QString &str)
Definition SvgUtil.cpp:459
QString alignmentToString(Alignment alignment) const
Definition SvgUtil.cpp:521