File indexing completed on 2024-06-23 04:27:03
0001 /* This file is part of the KDE project 0002 * SPDX-FileCopyrightText: 2007, 2010, 2011 Jan Hambrecht <jaham@gmx.net> 0003 * SPDX-FileCopyrightText: 2009-2010 Thomas Zander <zander@kde.org> 0004 * SPDX-FileCopyrightText: 2010 Carlos Licea <carlos@kdab.com> 0005 * SPDX-FileCopyrightText: 2010 Nokia Corporation and /or its subsidiary(-ies). 0006 * Contact: Suresh Chande suresh.chande@nokia.com 0007 * SPDX-FileCopyrightText: 2009-2010 Thorsten Zachmann <zachmann@kde.org> 0008 * 0009 * SPDX-License-Identifier: LGPL-2.0-or-later 0010 */ 0011 0012 #include <KoParameterShape_p.h> 0013 0014 #include "EnhancedPathShape.h" 0015 #include "EnhancedPathCommand.h" 0016 #include "EnhancedPathParameter.h" 0017 #include "EnhancedPathHandle.h" 0018 #include "EnhancedPathFormula.h" 0019 0020 #include <QPainterPath> 0021 0022 #include <KoXmlNS.h> 0023 #include <KoXmlWriter.h> 0024 #include <KoShapeSavingContext.h> 0025 #include <KoUnit.h> 0026 #include <KoPathPoint.h> 0027 0028 EnhancedPathShape::EnhancedPathShape(const QRect &viewBox) 0029 : m_viewBox(viewBox) 0030 , m_viewBoxOffset(0.0, 0.0) 0031 , m_mirrorVertically(false) 0032 , m_mirrorHorizontally(false) 0033 , m_pathStretchPointX(-1) 0034 , m_pathStretchPointY(-1) 0035 , m_cacheResults(false) 0036 { 0037 } 0038 0039 EnhancedPathShape::EnhancedPathShape(const EnhancedPathShape &rhs) 0040 : KoParameterShape(rhs), 0041 m_viewBox(rhs.m_viewBox), 0042 m_viewBound(rhs.m_viewBound), 0043 m_viewMatrix(rhs.m_viewMatrix), 0044 m_mirrorMatrix(rhs.m_mirrorMatrix), 0045 m_viewBoxOffset(rhs.m_viewBoxOffset), 0046 m_textArea(rhs.m_textArea), 0047 m_commands(rhs.m_commands), 0048 m_enhancedHandles(rhs.m_enhancedHandles), 0049 m_formulae(rhs.m_formulae), 0050 m_modifiers(rhs.m_modifiers), 0051 m_parameters(rhs.m_parameters), 0052 m_mirrorVertically(rhs.m_mirrorVertically), 0053 m_mirrorHorizontally(rhs.m_mirrorHorizontally), 0054 m_pathStretchPointX(rhs.m_pathStretchPointX), 0055 m_pathStretchPointY(rhs.m_pathStretchPointY), 0056 m_resultCache(rhs.m_resultCache), 0057 m_cacheResults(rhs.m_cacheResults) 0058 { 0059 } 0060 0061 EnhancedPathShape::~EnhancedPathShape() 0062 { 0063 reset(); 0064 } 0065 0066 KoShape *EnhancedPathShape::cloneShape() const 0067 { 0068 return new EnhancedPathShape(*this); 0069 } 0070 0071 void EnhancedPathShape::reset() 0072 { 0073 qDeleteAll(m_commands); 0074 m_commands.clear(); 0075 qDeleteAll(m_enhancedHandles); 0076 m_enhancedHandles.clear(); 0077 setHandles(QList<QPointF>()); 0078 qDeleteAll(m_formulae); 0079 m_formulae.clear(); 0080 qDeleteAll(m_parameters); 0081 m_parameters.clear(); 0082 m_modifiers.clear(); 0083 m_viewMatrix.reset(); 0084 m_viewBoxOffset = QPointF(); 0085 clear(); 0086 m_textArea.clear(); 0087 } 0088 0089 void EnhancedPathShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers) 0090 { 0091 Q_UNUSED(modifiers); 0092 EnhancedPathHandle *handle = m_enhancedHandles[ handleId ]; 0093 if (handle) { 0094 handle->changePosition(shapeToViewbox(point)); 0095 } 0096 } 0097 0098 void EnhancedPathShape::updatePath(const QSizeF &size) 0099 { 0100 if (isParametricShape()) { 0101 clear(); 0102 enableResultCache(true); 0103 0104 foreach (EnhancedPathCommand *cmd, m_commands) { 0105 cmd->execute(); 0106 } 0107 0108 enableResultCache(false); 0109 0110 qreal stretchPointsScale = 1; 0111 bool isStretched = useStretchPoints(size, stretchPointsScale); 0112 m_viewBound = outline().boundingRect(); 0113 m_mirrorMatrix.reset(); 0114 m_mirrorMatrix.translate(m_viewBound.center().x(), m_viewBound.center().y()); 0115 m_mirrorMatrix.scale(m_mirrorHorizontally ? -1 : 1, m_mirrorVertically ? -1 : 1); 0116 m_mirrorMatrix.translate(-m_viewBound.center().x(), -m_viewBound.center().y()); 0117 QTransform matrix(1.0, 0.0, 0.0, 1.0, m_viewBoxOffset.x(), m_viewBoxOffset.y()); 0118 0119 // if stretch points are set than stretch the path manually 0120 if (isStretched) { 0121 //if the path was stretched manually the stretch matrix is not more valid 0122 //and it has to be recalculated so that stretching in x and y direction is the same 0123 matrix.scale(stretchPointsScale, stretchPointsScale); 0124 matrix = m_mirrorMatrix * matrix; 0125 } else { 0126 matrix = m_mirrorMatrix * m_viewMatrix * matrix; 0127 } 0128 foreach (KoSubpath *subpath, subpaths()) { 0129 foreach (KoPathPoint *point, *subpath) { 0130 point->map(matrix); 0131 } 0132 } 0133 0134 const int handleCount = m_enhancedHandles.count(); 0135 QList<QPointF> handles; 0136 for (int i = 0; i < handleCount; ++i) { 0137 handles.append(matrix.map(m_enhancedHandles[i]->position())); 0138 } 0139 setHandles(handles); 0140 0141 normalize(); 0142 } 0143 } 0144 0145 void EnhancedPathShape::setSize(const QSizeF &newSize) 0146 { 0147 // handle offset 0148 KoParameterShape::setSize(newSize); 0149 0150 // calculate scaling factors from viewbox size to shape size 0151 qreal xScale = m_viewBound.width() == 0 ? 1 : newSize.width() / m_viewBound.width(); 0152 qreal yScale = m_viewBound.height() == 0 ? 1 : newSize.height() / m_viewBound.height(); 0153 0154 // create view matrix, take mirroring into account 0155 m_viewMatrix.reset(); 0156 m_viewMatrix.scale(xScale, yScale); 0157 0158 updatePath(newSize); 0159 } 0160 0161 QPointF EnhancedPathShape::normalize() 0162 { 0163 QPointF offset = KoParameterShape::normalize(); 0164 0165 m_viewBoxOffset -= offset; 0166 0167 return offset; 0168 } 0169 0170 QPointF EnhancedPathShape::shapeToViewbox(const QPointF &point) const 0171 { 0172 return (m_mirrorMatrix * m_viewMatrix).inverted().map(point - m_viewBoxOffset); 0173 } 0174 0175 void EnhancedPathShape::evaluateHandles() 0176 { 0177 const int handleCount = m_enhancedHandles.count(); 0178 QList<QPointF> handles; 0179 for (int i = 0; i < handleCount; ++i) { 0180 handles.append(m_enhancedHandles[i]->position()); 0181 } 0182 setHandles(handles); 0183 } 0184 0185 QRect EnhancedPathShape::viewBox() const 0186 { 0187 return m_viewBox; 0188 } 0189 0190 qreal EnhancedPathShape::evaluateReference(const QString &reference) 0191 { 0192 if (reference.isEmpty()) { 0193 return 0.0; 0194 } 0195 0196 const char c = reference[0].toLatin1(); 0197 0198 qreal res = 0.0; 0199 0200 switch (c) { 0201 // referenced modifier 0202 case '$': { 0203 bool success = false; 0204 int modifierIndex = reference.mid(1).toInt(&success); 0205 res = m_modifiers.value(modifierIndex); 0206 break; 0207 } 0208 // referenced formula 0209 case '?': { 0210 QString fname = reference.mid(1); 0211 if (m_cacheResults && m_resultCache.contains(fname)) { 0212 res = m_resultCache.value(fname); 0213 } else { 0214 FormulaStore::const_iterator formulaIt = m_formulae.constFind(fname); 0215 if (formulaIt != m_formulae.constEnd()) { 0216 EnhancedPathFormula *formula = formulaIt.value(); 0217 if (formula) { 0218 res = formula->evaluate(); 0219 if (m_cacheResults) { 0220 m_resultCache.insert(fname, res); 0221 } 0222 } 0223 } 0224 } 0225 break; 0226 } 0227 // maybe an identifier ? 0228 default: 0229 EnhancedPathNamedParameter p(reference, this); 0230 res = p.evaluate(); 0231 break; 0232 } 0233 0234 return res; 0235 } 0236 0237 qreal EnhancedPathShape::evaluateConstantOrReference(const QString &val) 0238 { 0239 bool ok = true; 0240 qreal res = val.toDouble(&ok); 0241 if (ok) { 0242 return res; 0243 } 0244 return evaluateReference(val); 0245 } 0246 0247 void EnhancedPathShape::modifyReference(const QString &reference, qreal value) 0248 { 0249 if (reference.isEmpty()) { 0250 return; 0251 } 0252 0253 const char c = reference[0].toLatin1(); 0254 0255 if (c == '$') { 0256 bool success = false; 0257 int modifierIndex = reference.mid(1).toInt(&success); 0258 if (modifierIndex >= 0 && modifierIndex < m_modifiers.count()) { 0259 m_modifiers[modifierIndex] = value; 0260 } 0261 } 0262 } 0263 0264 EnhancedPathParameter *EnhancedPathShape::parameter(const QString &text) 0265 { 0266 Q_ASSERT(! text.isEmpty()); 0267 0268 ParameterStore::const_iterator parameterIt = m_parameters.constFind(text); 0269 if (parameterIt != m_parameters.constEnd()) { 0270 return parameterIt.value(); 0271 } else { 0272 EnhancedPathParameter *parameter = 0; 0273 const char c = text[0].toLatin1(); 0274 if (c == '$' || c == '?') { 0275 parameter = new EnhancedPathReferenceParameter(text, this); 0276 } else { 0277 bool success = false; 0278 qreal constant = text.toDouble(&success); 0279 if (success) { 0280 parameter = new EnhancedPathConstantParameter(constant, this); 0281 } else { 0282 Identifier identifier = EnhancedPathNamedParameter::identifierFromString(text); 0283 if (identifier != IdentifierUnknown) { 0284 parameter = new EnhancedPathNamedParameter(identifier, this); 0285 } 0286 } 0287 } 0288 0289 if (parameter) { 0290 m_parameters[text] = parameter; 0291 } 0292 0293 return parameter; 0294 } 0295 } 0296 0297 void EnhancedPathShape::addFormula(const QString &name, const QString &formula) 0298 { 0299 if (name.isEmpty() || formula.isEmpty()) { 0300 return; 0301 } 0302 0303 m_formulae[name] = new EnhancedPathFormula(formula, this); 0304 } 0305 0306 void EnhancedPathShape::addHandle(const QMap<QString, QVariant> &handle) 0307 { 0308 if (handle.isEmpty()) { 0309 return; 0310 } 0311 0312 if (!handle.contains("draw:handle-position")) { 0313 return; 0314 } 0315 QVariant position = handle.value("draw:handle-position"); 0316 0317 QStringList tokens = position.toString().simplified().split(' '); 0318 if (tokens.count() < 2) { 0319 return; 0320 } 0321 0322 EnhancedPathHandle *newHandle = new EnhancedPathHandle(this); 0323 newHandle->setPosition(parameter(tokens[0]), parameter(tokens[1])); 0324 0325 // check if we have a polar handle 0326 if (handle.contains("draw:handle-polar")) { 0327 QVariant polar = handle.value("draw:handle-polar"); 0328 QStringList tokens = polar.toString().simplified().split(' '); 0329 if (tokens.count() == 2) { 0330 newHandle->setPolarCenter(parameter(tokens[0]), parameter(tokens[1])); 0331 0332 QVariant minRadius = handle.value("draw:handle-radius-range-minimum"); 0333 QVariant maxRadius = handle.value("draw:handle-radius-range-maximum"); 0334 if (minRadius.isValid() && maxRadius.isValid()) { 0335 newHandle->setRadiusRange(parameter(minRadius.toString()), parameter(maxRadius.toString())); 0336 } 0337 } 0338 } else { 0339 QVariant minX = handle.value("draw:handle-range-x-minimum"); 0340 QVariant maxX = handle.value("draw:handle-range-x-maximum"); 0341 if (minX.isValid() && maxX.isValid()) { 0342 newHandle->setRangeX(parameter(minX.toString()), parameter(maxX.toString())); 0343 } 0344 0345 QVariant minY = handle.value("draw:handle-range-y-minimum"); 0346 QVariant maxY = handle.value("draw:handle-range-y-maximum"); 0347 if (minY.isValid() && maxY.isValid()) { 0348 newHandle->setRangeY(parameter(minY.toString()), parameter(maxY.toString())); 0349 } 0350 } 0351 0352 m_enhancedHandles.append(newHandle); 0353 0354 evaluateHandles(); 0355 } 0356 0357 void EnhancedPathShape::addModifiers(const QString &modifiers) 0358 { 0359 if (modifiers.isEmpty()) { 0360 return; 0361 } 0362 0363 QStringList tokens = modifiers.simplified().split(' '); 0364 int tokenCount = tokens.count(); 0365 for (int i = 0; i < tokenCount; ++i) { 0366 m_modifiers.append(tokens[i].toDouble()); 0367 } 0368 } 0369 0370 void EnhancedPathShape::addCommand(const QString &command) 0371 { 0372 addCommand(command, true); 0373 } 0374 0375 void EnhancedPathShape::addCommand(const QString &command, bool triggerUpdate) 0376 { 0377 QString commandStr = command.simplified(); 0378 if (commandStr.isEmpty()) { 0379 return; 0380 } 0381 0382 // the first character is the command 0383 EnhancedPathCommand *cmd = new EnhancedPathCommand(commandStr[0], this); 0384 0385 // strip command char 0386 commandStr = commandStr.mid(1).simplified(); 0387 0388 // now parse the command parameters 0389 if (!commandStr.isEmpty()) { 0390 QStringList tokens = commandStr.split(' '); 0391 for (int i = 0; i < tokens.count(); ++i) { 0392 cmd->addParameter(parameter(tokens[i])); 0393 } 0394 } 0395 m_commands.append(cmd); 0396 0397 if (triggerUpdate) { 0398 updatePath(size()); 0399 } 0400 } 0401 0402 bool EnhancedPathShape::useStretchPoints(const QSizeF &size, qreal &scale) 0403 { 0404 bool retval = false; 0405 if (m_pathStretchPointX != -1 && m_pathStretchPointY != -1) { 0406 qreal scaleX = size.width(); 0407 qreal scaleY = size.height(); 0408 if (qreal(m_viewBox.width()) / m_viewBox.height() < qreal(scaleX) / scaleY) { 0409 qreal deltaX = (scaleX * m_viewBox.height()) / scaleY - m_viewBox.width(); 0410 foreach (KoSubpath *subpath, subpaths()) { 0411 foreach (KoPathPoint *currPoint, *subpath) { 0412 if (currPoint->point().x() >= m_pathStretchPointX && 0413 currPoint->controlPoint1().x() >= m_pathStretchPointX && 0414 currPoint->controlPoint2().x() >= m_pathStretchPointX) { 0415 currPoint->setPoint(QPointF(currPoint->point().x() + deltaX, currPoint->point().y())); 0416 currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x() + deltaX, 0417 currPoint->controlPoint1().y())); 0418 currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x() + deltaX, 0419 currPoint->controlPoint2().y())); 0420 retval = true; 0421 } 0422 } 0423 } 0424 scale = scaleY / m_viewBox.height(); 0425 } else if (qreal(m_viewBox.width()) / m_viewBox.height() > qreal(scaleX) / scaleY) { 0426 qreal deltaY = (m_viewBox.width() * scaleY) / scaleX - m_viewBox.height(); 0427 foreach (KoSubpath *subpath, subpaths()) { 0428 foreach (KoPathPoint *currPoint, *subpath) { 0429 if (currPoint->point().y() >= m_pathStretchPointY && 0430 currPoint->controlPoint1().y() >= m_pathStretchPointY && 0431 currPoint->controlPoint2().y() >= m_pathStretchPointY) { 0432 currPoint->setPoint(QPointF(currPoint->point().x(), currPoint->point().y() + deltaY)); 0433 currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x(), 0434 currPoint->controlPoint1().y() + deltaY)); 0435 currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x(), 0436 currPoint->controlPoint2().y() + deltaY)); 0437 retval = true; 0438 } 0439 } 0440 } 0441 scale = scaleX / m_viewBox.width(); 0442 } 0443 0444 notifyPointsChanged(); 0445 } 0446 return retval; 0447 } 0448 0449 void EnhancedPathShape::parsePathData(const QString &data) 0450 { 0451 if (data.isEmpty()) { 0452 return; 0453 } 0454 0455 int start = -1; 0456 bool separator = true; 0457 for (int i = 0; i < data.length(); ++i) { 0458 QChar ch = data.at(i); 0459 ushort uni_ch = ch.unicode(); 0460 if (separator && (uni_ch == 'M' || uni_ch == 'L' 0461 || uni_ch == 'C' || uni_ch == 'Z' 0462 || uni_ch == 'N' || uni_ch == 'F' 0463 || uni_ch == 'S' || uni_ch == 'T' 0464 || uni_ch == 'U' || uni_ch == 'A' 0465 || uni_ch == 'B' || uni_ch == 'W' 0466 || uni_ch == 'V' || uni_ch == 'X' 0467 || uni_ch == 'Y' || uni_ch == 'Q')) { 0468 if (start != -1) { // process last chars 0469 addCommand(data.mid(start, i - start), false); 0470 } 0471 start = i; 0472 } 0473 separator = ch.isSpace(); 0474 } 0475 if (start < data.length()) { 0476 addCommand(data.mid(start)); 0477 } 0478 if (start != -1) { 0479 updatePath(size()); 0480 } 0481 } 0482 0483 void EnhancedPathShape::setMirrorHorizontally(bool mirrorHorizontally) 0484 { 0485 if (m_mirrorHorizontally != mirrorHorizontally) { 0486 m_mirrorHorizontally = mirrorHorizontally; 0487 updatePath(size()); 0488 } 0489 0490 } 0491 0492 void EnhancedPathShape::setMirrorVertically(bool mirrorVertically) 0493 { 0494 if (m_mirrorVertically != mirrorVertically) { 0495 m_mirrorVertically = mirrorVertically; 0496 updatePath(size()); 0497 } 0498 } 0499 0500 void EnhancedPathShape::shapeChanged(ChangeType type, KoShape *shape) 0501 { 0502 KoParameterShape::shapeChanged(type, shape); 0503 0504 if (!shape || shape == this) { 0505 if (type == ParentChanged || type == ParameterChanged) { 0506 updateTextArea(); 0507 } 0508 } 0509 } 0510 0511 void EnhancedPathShape::updateTextArea() 0512 { 0513 if (m_textArea.size() >= 4) { 0514 QRectF r = m_viewBox; 0515 r.setLeft(evaluateConstantOrReference(m_textArea[0])); 0516 r.setTop(evaluateConstantOrReference(m_textArea[1])); 0517 r.setRight(evaluateConstantOrReference(m_textArea[2])); 0518 r.setBottom(evaluateConstantOrReference(m_textArea[3])); 0519 r = m_viewMatrix.mapRect(r).translated(m_viewBoxOffset); 0520 setPreferredTextRect(r); 0521 } 0522 } 0523 0524 void EnhancedPathShape::enableResultCache(bool enable) 0525 { 0526 m_resultCache.clear(); 0527 m_cacheResults = enable; 0528 } 0529 0530 void EnhancedPathShape::setPathStretchPointX(qreal pathStretchPointX) 0531 { 0532 if (m_pathStretchPointX != pathStretchPointX) { 0533 m_pathStretchPointX = pathStretchPointX; 0534 } 0535 0536 } 0537 0538 void EnhancedPathShape::setPathStretchPointY(qreal pathStretchPointY) 0539 { 0540 if (m_pathStretchPointY != pathStretchPointY) { 0541 m_pathStretchPointY = pathStretchPointY; 0542 } 0543 0544 }