File indexing completed on 2024-06-09 04:20:52
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2009 Jan Hambrecht <jaham@gmx.net> 0003 * 0004 * SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "SvgUtil.h" 0008 #include "SvgGraphicContext.h" 0009 0010 #include <KoUnit.h> 0011 0012 #include <QString> 0013 #include <QRectF> 0014 #include <QStringList> 0015 #include <QFontMetrics> 0016 #include <QRegExp> 0017 0018 #include <math.h> 0019 #include "kis_debug.h" 0020 #include "kis_global.h" 0021 0022 #include <KoXmlWriter.h> 0023 #include "kis_dom_utils.h" 0024 0025 #define DPI 72.0 0026 0027 #define DEG2RAD(degree) degree/180.0*M_PI 0028 0029 double SvgUtil::fromUserSpace(double value) 0030 { 0031 return value; 0032 } 0033 0034 double SvgUtil::toUserSpace(double value) 0035 { 0036 return value; 0037 } 0038 0039 double SvgUtil::ptToPx(SvgGraphicsContext *gc, double value) 0040 { 0041 return value * gc->pixelsPerInch / DPI; 0042 } 0043 0044 QPointF SvgUtil::toUserSpace(const QPointF &point) 0045 { 0046 return QPointF(toUserSpace(point.x()), toUserSpace(point.y())); 0047 } 0048 0049 QRectF SvgUtil::toUserSpace(const QRectF &rect) 0050 { 0051 return QRectF(toUserSpace(rect.topLeft()), toUserSpace(rect.size())); 0052 } 0053 0054 QSizeF SvgUtil::toUserSpace(const QSizeF &size) 0055 { 0056 return QSizeF(toUserSpace(size.width()), toUserSpace(size.height())); 0057 } 0058 0059 QString SvgUtil::toPercentage(qreal value) 0060 { 0061 return KisDomUtils::toString(value * 100.0) + "%"; 0062 } 0063 0064 double SvgUtil::fromPercentage(QString s, bool *ok) 0065 { 0066 if (s.endsWith('%')) 0067 return KisDomUtils::toDouble(s.remove('%'), ok) / 100.0; 0068 else 0069 return KisDomUtils::toDouble(s, ok); 0070 } 0071 0072 QPointF SvgUtil::objectToUserSpace(const QPointF &position, const QRectF &objectBound) 0073 { 0074 qreal x = objectBound.left() + position.x() * objectBound.width(); 0075 qreal y = objectBound.top() + position.y() * objectBound.height(); 0076 return QPointF(x, y); 0077 } 0078 0079 QSizeF SvgUtil::objectToUserSpace(const QSizeF &size, const QRectF &objectBound) 0080 { 0081 qreal w = size.width() * objectBound.width(); 0082 qreal h = size.height() * objectBound.height(); 0083 return QSizeF(w, h); 0084 } 0085 0086 QPointF SvgUtil::userSpaceToObject(const QPointF &position, const QRectF &objectBound) 0087 { 0088 qreal x = 0.0; 0089 if (objectBound.width() != 0) 0090 x = (position.x() - objectBound.x()) / objectBound.width(); 0091 qreal y = 0.0; 0092 if (objectBound.height() != 0) 0093 y = (position.y() - objectBound.y()) / objectBound.height(); 0094 return QPointF(x, y); 0095 } 0096 0097 QSizeF SvgUtil::userSpaceToObject(const QSizeF &size, const QRectF &objectBound) 0098 { 0099 qreal w = objectBound.width() != 0 ? size.width() / objectBound.width() : 0.0; 0100 qreal h = objectBound.height() != 0 ? size.height() / objectBound.height() : 0.0; 0101 return QSizeF(w, h); 0102 } 0103 0104 QString SvgUtil::transformToString(const QTransform &transform) 0105 { 0106 if (transform.isIdentity()) 0107 return QString(); 0108 0109 if (transform.type() == QTransform::TxTranslate) { 0110 return QString("translate(%1, %2)") 0111 .arg(KisDomUtils::toString(toUserSpace(transform.dx()))) 0112 .arg(KisDomUtils::toString(toUserSpace(transform.dy()))); 0113 } else { 0114 return QString("matrix(%1 %2 %3 %4 %5 %6)") 0115 .arg(KisDomUtils::toString(transform.m11())) 0116 .arg(KisDomUtils::toString(transform.m12())) 0117 .arg(KisDomUtils::toString(transform.m21())) 0118 .arg(KisDomUtils::toString(transform.m22())) 0119 .arg(KisDomUtils::toString(toUserSpace(transform.dx()))) 0120 .arg(KisDomUtils::toString(toUserSpace(transform.dy()))); 0121 } 0122 } 0123 0124 void SvgUtil::writeTransformAttributeLazy(const QString &name, const QTransform &transform, KoXmlWriter &shapeWriter) 0125 { 0126 const QString value = transformToString(transform); 0127 0128 if (!value.isEmpty()) { 0129 shapeWriter.addAttribute(name.toLatin1().data(), value); 0130 } 0131 } 0132 0133 bool SvgUtil::parseViewBox(const QDomElement &e, 0134 const QRectF &elementBounds, 0135 QRectF *_viewRect, QTransform *_viewTransform) 0136 { 0137 KIS_ASSERT(_viewRect); 0138 KIS_ASSERT(_viewTransform); 0139 0140 QString viewBoxStr = e.attribute("viewBox"); 0141 if (viewBoxStr.isEmpty()) return false; 0142 0143 bool result = false; 0144 0145 QRectF viewBoxRect; 0146 // this is a workaround for bug 260429 for a file generated by blender 0147 // who has px in the viewbox which is wrong. 0148 // reported as bug https://developer.blender.org/T30971 0149 viewBoxStr.remove("px"); 0150 0151 QStringList points = viewBoxStr.replace(',', ' ').simplified().split(' '); 0152 if (points.count() == 4) { 0153 viewBoxRect.setX(SvgUtil::fromUserSpace(points[0].toDouble())); 0154 viewBoxRect.setY(SvgUtil::fromUserSpace(points[1].toDouble())); 0155 viewBoxRect.setWidth(SvgUtil::fromUserSpace(points[2].toDouble())); 0156 viewBoxRect.setHeight(SvgUtil::fromUserSpace(points[3].toDouble())); 0157 0158 result = true; 0159 } else { 0160 // TODO: WARNING! 0161 } 0162 0163 if (!result) return false; 0164 0165 qreal scaleX = 1; 0166 if (!qFuzzyCompare(elementBounds.width(), viewBoxRect.width())) { 0167 scaleX = elementBounds.width() / viewBoxRect.width(); 0168 } 0169 qreal scaleY = 1; 0170 if (!qFuzzyCompare(elementBounds.height(), viewBoxRect.height())) { 0171 scaleY = elementBounds.height() / viewBoxRect.height(); 0172 } 0173 0174 QTransform viewBoxTransform = 0175 QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) * 0176 QTransform::fromScale(scaleX, scaleY) * 0177 QTransform::fromTranslate(elementBounds.x(), elementBounds.y()); 0178 0179 const QString aspectString = e.attribute("preserveAspectRatio"); 0180 // give initial value if value not defined 0181 PreserveAspectRatioParser p( (!aspectString.isEmpty())? aspectString : QString("xMidYMid meet")); 0182 parseAspectRatio(p, elementBounds, viewBoxRect, &viewBoxTransform); 0183 0184 *_viewRect = viewBoxRect; 0185 *_viewTransform = viewBoxTransform; 0186 0187 return result; 0188 } 0189 0190 void SvgUtil::parseAspectRatio(const PreserveAspectRatioParser &p, const QRectF &elementBounds, const QRectF &viewBoxRect, QTransform *_viewTransform) 0191 { 0192 if (p.mode != Qt::IgnoreAspectRatio) { 0193 QTransform viewBoxTransform = *_viewTransform; 0194 0195 const qreal tan1 = viewBoxRect.height() / viewBoxRect.width(); 0196 const qreal tan2 = elementBounds.height() / elementBounds.width(); 0197 0198 const qreal uniformScale = 0199 (p.mode == Qt::KeepAspectRatioByExpanding) ^ (tan1 > tan2) ? 0200 elementBounds.height() / viewBoxRect.height() : 0201 elementBounds.width() / viewBoxRect.width(); 0202 0203 viewBoxTransform = 0204 QTransform::fromTranslate(-viewBoxRect.x(), -viewBoxRect.y()) * 0205 QTransform::fromScale(uniformScale, uniformScale) * 0206 QTransform::fromTranslate(elementBounds.x(), elementBounds.y()); 0207 0208 const QPointF viewBoxAnchor = viewBoxTransform.map(p.rectAnchorPoint(viewBoxRect)); 0209 const QPointF elementAnchor = p.rectAnchorPoint(elementBounds); 0210 const QPointF offset = elementAnchor - viewBoxAnchor; 0211 0212 viewBoxTransform = viewBoxTransform * QTransform::fromTranslate(offset.x(), offset.y()); 0213 0214 *_viewTransform = viewBoxTransform; 0215 } 0216 } 0217 0218 qreal SvgUtil::parseUnit(SvgGraphicsContext *gc, const QString &unit, bool horiz, bool vert, const QRectF &bbox) 0219 { 0220 if (unit.isEmpty()) 0221 return 0.0; 0222 QByteArray unitLatin1 = unit.toLatin1(); 0223 // TODO : percentage? 0224 const char *start = unitLatin1.data(); 0225 if (!start) { 0226 return 0.0; 0227 } 0228 qreal value = 0.0; 0229 const char *end = parseNumber(start, value); 0230 0231 if (int(end - start) < unit.length()) { 0232 if (unit.right(2) == "px") 0233 value = SvgUtil::fromUserSpace(value); 0234 else if (unit.right(2) == "pt") 0235 value = ptToPx(gc, value); 0236 else if (unit.right(2) == "cm") 0237 value = ptToPx(gc, CM_TO_POINT(value)); 0238 else if (unit.right(2) == "pc") 0239 value = ptToPx(gc, PI_TO_POINT(value)); 0240 else if (unit.right(2) == "mm") 0241 value = ptToPx(gc, MM_TO_POINT(value)); 0242 else if (unit.right(2) == "in") 0243 value = ptToPx(gc, INCH_TO_POINT(value)); 0244 else if (unit.right(2) == "em") { 0245 value = value * gc->textProperties.propertyOrDefault(KoSvgTextProperties::FontSizeId).toReal(); 0246 } 0247 else if (unit.right(2) == "ex") { 0248 0249 QFontMetrics metrics(gc->textProperties.generateFont()); 0250 value = value * metrics.xHeight(); 0251 } else if (unit.right(1) == "%") { 0252 if (horiz && vert) 0253 value = (value / 100.0) * (sqrt(pow(bbox.width(), 2) + pow(bbox.height(), 2)) / sqrt(2.0)); 0254 else if (horiz) 0255 value = (value / 100.0) * bbox.width(); 0256 else if (vert) 0257 value = (value / 100.0) * bbox.height(); 0258 } 0259 } else { 0260 value = SvgUtil::fromUserSpace(value); 0261 } 0262 /*else 0263 { 0264 if( m_gc.top() ) 0265 { 0266 if( horiz && vert ) 0267 value *= sqrt( pow( m_gc.top()->matrix.m11(), 2 ) + pow( m_gc.top()->matrix.m22(), 2 ) ) / sqrt( 2.0 ); 0268 else if( horiz ) 0269 value /= m_gc.top()->matrix.m11(); 0270 else if( vert ) 0271 value /= m_gc.top()->matrix.m22(); 0272 } 0273 }*/ 0274 //value *= 90.0 / DPI; 0275 0276 return value; 0277 } 0278 0279 qreal SvgUtil::parseUnitX(SvgGraphicsContext *gc, const QString &unit) 0280 { 0281 if (gc->forcePercentage) { 0282 return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.width(); 0283 } else { 0284 return SvgUtil::parseUnit(gc, unit, true, false, gc->currentBoundingBox); 0285 } 0286 } 0287 0288 qreal SvgUtil::parseUnitY(SvgGraphicsContext *gc, const QString &unit) 0289 { 0290 if (gc->forcePercentage) { 0291 return SvgUtil::fromPercentage(unit) * gc->currentBoundingBox.height(); 0292 } else { 0293 return SvgUtil::parseUnit(gc, unit, false, true, gc->currentBoundingBox); 0294 } 0295 } 0296 0297 qreal SvgUtil::parseUnitXY(SvgGraphicsContext *gc, const QString &unit) 0298 { 0299 if (gc->forcePercentage) { 0300 const qreal value = SvgUtil::fromPercentage(unit); 0301 return value * sqrt(pow(gc->currentBoundingBox.width(), 2) + pow(gc->currentBoundingBox.height(), 2)) / sqrt(2.0); 0302 } else { 0303 return SvgUtil::parseUnit(gc, unit, true, true, gc->currentBoundingBox); 0304 } 0305 } 0306 0307 qreal SvgUtil::parseUnitAngular(SvgGraphicsContext *gc, const QString &unit) 0308 { 0309 Q_UNUSED(gc); 0310 0311 qreal value = 0.0; 0312 0313 if (unit.isEmpty()) return value; 0314 QByteArray unitLatin1 = unit.toLower().toLatin1(); 0315 0316 const char *start = unitLatin1.data(); 0317 if (!start) return value; 0318 0319 const char *end = parseNumber(start, value); 0320 0321 if (int(end - start) < unit.length()) { 0322 if (unit.right(3) == "deg") { 0323 value = kisDegreesToRadians(value); 0324 } else if (unit.right(4) == "grad") { 0325 value *= M_PI / 200; 0326 } else if (unit.right(3) == "rad") { 0327 // noop! 0328 } else { 0329 value = kisDegreesToRadians(value); 0330 } 0331 } else { 0332 value = kisDegreesToRadians(value); 0333 } 0334 0335 return value; 0336 } 0337 0338 qreal SvgUtil::parseNumber(const QString &string) 0339 { 0340 qreal value = 0.0; 0341 0342 if (string.isEmpty()) return value; 0343 QByteArray unitLatin1 = string.toLatin1(); 0344 0345 const char *start = unitLatin1.data(); 0346 if (!start) return value; 0347 0348 const char *end = parseNumber(start, value); 0349 KIS_SAFE_ASSERT_RECOVER_NOOP(int(end - start) == string.length()); 0350 return value; 0351 } 0352 0353 const char * SvgUtil::parseNumber(const char *ptr, qreal &number) 0354 { 0355 int integer, exponent; 0356 qreal decimal, frac; 0357 int sign, expsign; 0358 0359 exponent = 0; 0360 integer = 0; 0361 frac = 1.0; 0362 decimal = 0; 0363 sign = 1; 0364 expsign = 1; 0365 0366 // read the sign 0367 if (*ptr == '+') { 0368 ptr++; 0369 } else if (*ptr == '-') { 0370 ptr++; 0371 sign = -1; 0372 } 0373 0374 // read the integer part 0375 while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') 0376 integer = (integer * 10) + *(ptr++) - '0'; 0377 if (*ptr == '.') { // read the decimals 0378 ptr++; 0379 while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') 0380 decimal += (*(ptr++) - '0') * (frac *= 0.1); 0381 } 0382 0383 if (*ptr == 'e' || *ptr == 'E') { // read the exponent part 0384 ptr++; 0385 0386 // read the sign of the exponent 0387 if (*ptr == '+') { 0388 ptr++; 0389 } else if (*ptr == '-') { 0390 ptr++; 0391 expsign = -1; 0392 } 0393 0394 exponent = 0; 0395 while (*ptr != '\0' && *ptr >= '0' && *ptr <= '9') { 0396 exponent *= 10; 0397 exponent += *ptr - '0'; 0398 ptr++; 0399 } 0400 } 0401 number = integer + decimal; 0402 number *= sign * pow((double)10, double(expsign * exponent)); 0403 0404 return ptr; 0405 } 0406 0407 QString SvgUtil::mapExtendedShapeTag(const QString &tagName, const QDomElement &element) 0408 { 0409 QString result = tagName; 0410 0411 if (tagName == "path") { 0412 QString kritaType = element.attribute("krita:type", ""); 0413 QString sodipodiType = element.attribute("sodipodi:type", ""); 0414 0415 if (kritaType == "arc") { 0416 result = "krita:arc"; 0417 } else if (sodipodiType == "arc") { 0418 result = "sodipodi:arc"; 0419 } 0420 } 0421 0422 return result; 0423 } 0424 0425 QStringList SvgUtil::simplifyList(const QString &str) 0426 { 0427 QString attribute = str; 0428 attribute.replace(',', ' '); 0429 attribute.remove('\r'); 0430 attribute.remove('\n'); 0431 #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 0432 return attribute.simplified().split(' ', Qt::SkipEmptyParts); 0433 #else 0434 return attribute.simplified().split(' ', QString::SkipEmptyParts); 0435 #endif 0436 } 0437 0438 SvgUtil::PreserveAspectRatioParser::PreserveAspectRatioParser(const QString &str) 0439 { 0440 QRegExp rexp("(defer)?\\s*(none|(x(Min|Max|Mid)Y(Min|Max|Mid)))\\s*(meet|slice)?", Qt::CaseInsensitive); 0441 int index = rexp.indexIn(str.toLower()); 0442 0443 if (index >= 0) { 0444 if (rexp.cap(1) == "defer") { 0445 defer = true; 0446 } 0447 0448 if (rexp.cap(2) != "none") { 0449 xAlignment = alignmentFromString(rexp.cap(4)); 0450 yAlignment = alignmentFromString(rexp.cap(5)); 0451 mode = rexp.cap(6) == "slice" ? 0452 Qt::KeepAspectRatioByExpanding : Qt::KeepAspectRatio; 0453 } 0454 } 0455 } 0456 0457 QPointF SvgUtil::PreserveAspectRatioParser::rectAnchorPoint(const QRectF &rc) const 0458 { 0459 return QPointF(alignedValue(rc.x(), rc.x() + rc.width(), xAlignment), 0460 alignedValue(rc.y(), rc.y() + rc.height(), yAlignment)); 0461 } 0462 0463 QString SvgUtil::PreserveAspectRatioParser::toString() const 0464 { 0465 QString result; 0466 0467 if (!defer && 0468 xAlignment == Middle && 0469 yAlignment == Middle && 0470 mode == Qt::KeepAspectRatio) { 0471 0472 return result; 0473 } 0474 0475 if (defer) { 0476 result += "defer "; 0477 } 0478 0479 if (mode == Qt::IgnoreAspectRatio) { 0480 result += "none"; 0481 } else { 0482 result += QString("x%1Y%2") 0483 .arg(alignmentToString(xAlignment)) 0484 .arg(alignmentToString(yAlignment)); 0485 0486 if (mode == Qt::KeepAspectRatioByExpanding) { 0487 result += " slice"; 0488 } 0489 } 0490 0491 return result; 0492 } 0493 0494 SvgUtil::PreserveAspectRatioParser::Alignment SvgUtil::PreserveAspectRatioParser::alignmentFromString(const QString &str) const { 0495 return 0496 str == "max" ? Max : 0497 str == "mid" ? Middle : Min; 0498 } 0499 0500 QString SvgUtil::PreserveAspectRatioParser::alignmentToString(SvgUtil::PreserveAspectRatioParser::Alignment alignment) const 0501 { 0502 return 0503 alignment == Max ? "Max" : 0504 alignment == Min ? "Min" : 0505 "Mid"; 0506 0507 } 0508 0509 qreal SvgUtil::PreserveAspectRatioParser::alignedValue(qreal min, qreal max, SvgUtil::PreserveAspectRatioParser::Alignment alignment) 0510 { 0511 qreal result = min; 0512 0513 switch (alignment) { 0514 case Min: 0515 result = min; 0516 break; 0517 case Middle: 0518 result = 0.5 * (min + max); 0519 break; 0520 case Max: 0521 result = max; 0522 break; 0523 } 0524 0525 return result; 0526 }