Krita Source Code Documentation
Loading...
Searching...
No Matches
EnhancedPathShape.cpp
Go to the documentation of this file.
1/* This file is part of the KDE project
2 * SPDX-FileCopyrightText: 2007, 2010, 2011 Jan Hambrecht <jaham@gmx.net>
3 * SPDX-FileCopyrightText: 2009-2010 Thomas Zander <zander@kde.org>
4 * SPDX-FileCopyrightText: 2010 Carlos Licea <carlos@kdab.com>
5 * SPDX-FileCopyrightText: 2010 Nokia Corporation and /or its subsidiary(-ies).
6 * Contact: Suresh Chande suresh.chande@nokia.com
7 * SPDX-FileCopyrightText: 2009-2010 Thorsten Zachmann <zachmann@kde.org>
8 *
9 * SPDX-License-Identifier: LGPL-2.0-or-later
10 */
11
12#include <KoParameterShape_p.h>
13
14#include "EnhancedPathShape.h"
15#include "EnhancedPathCommand.h"
17#include "EnhancedPathHandle.h"
18#include "EnhancedPathFormula.h"
19
20#include <QPainterPath>
21
22#include <KoXmlNS.h>
23#include <KoXmlWriter.h>
25#include <KoUnit.h>
26#include <KoPathPoint.h>
27
29 : m_viewBox(viewBox)
30 , m_viewBoxOffset(0.0, 0.0)
31 , m_mirrorVertically(false)
32 , m_mirrorHorizontally(false)
33 , m_pathStretchPointX(-1)
34 , m_pathStretchPointY(-1)
35 , m_cacheResults(false)
36{
37}
38
40 : KoParameterShape(rhs),
41 m_viewBox(rhs.m_viewBox),
42 m_viewBound(rhs.m_viewBound),
43 m_viewMatrix(rhs.m_viewMatrix),
44 m_mirrorMatrix(rhs.m_mirrorMatrix),
45 m_viewBoxOffset(rhs.m_viewBoxOffset),
46 m_textArea(rhs.m_textArea),
47 m_commands(rhs.m_commands),
48 m_enhancedHandles(rhs.m_enhancedHandles),
49 m_formulae(rhs.m_formulae),
50 m_modifiers(rhs.m_modifiers),
51 m_parameters(rhs.m_parameters),
52 m_mirrorVertically(rhs.m_mirrorVertically),
53 m_mirrorHorizontally(rhs.m_mirrorHorizontally),
54 m_pathStretchPointX(rhs.m_pathStretchPointX),
55 m_pathStretchPointY(rhs.m_pathStretchPointY),
56 m_resultCache(rhs.m_resultCache),
57 m_cacheResults(rhs.m_cacheResults)
58{
59}
60
65
67{
68 return new EnhancedPathShape(*this);
69}
70
72{
73 qDeleteAll(m_commands);
74 m_commands.clear();
75 qDeleteAll(m_enhancedHandles);
76 m_enhancedHandles.clear();
78 qDeleteAll(m_formulae);
79 m_formulae.clear();
80 qDeleteAll(m_parameters);
81 m_parameters.clear();
82 m_modifiers.clear();
83 m_viewMatrix.reset();
84 m_viewBoxOffset = QPointF();
85 clear();
86 m_textArea.clear();
87}
88
89void EnhancedPathShape::moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers)
90{
91 Q_UNUSED(modifiers);
92 EnhancedPathHandle *handle = m_enhancedHandles[ handleId ];
93 if (handle) {
94 handle->changePosition(shapeToViewbox(point));
95 }
96}
97
98void EnhancedPathShape::updatePath(const QSizeF &size)
99{
100 if (isParametricShape()) {
101 clear();
102 enableResultCache(true);
103
104 foreach (EnhancedPathCommand *cmd, m_commands) {
105 cmd->execute();
106 }
107
108 enableResultCache(false);
109
110 qreal stretchPointsScale = 1;
111 bool isStretched = useStretchPoints(size, stretchPointsScale);
112 m_viewBound = outline().boundingRect();
113 m_mirrorMatrix.reset();
114 m_mirrorMatrix.translate(m_viewBound.center().x(), m_viewBound.center().y());
116 m_mirrorMatrix.translate(-m_viewBound.center().x(), -m_viewBound.center().y());
117 QTransform matrix(1.0, 0.0, 0.0, 1.0, m_viewBoxOffset.x(), m_viewBoxOffset.y());
118
119 // if stretch points are set than stretch the path manually
120 if (isStretched) {
121 //if the path was stretched manually the stretch matrix is not more valid
122 //and it has to be recalculated so that stretching in x and y direction is the same
123 matrix.scale(stretchPointsScale, stretchPointsScale);
124 matrix = m_mirrorMatrix * matrix;
125 } else {
126 matrix = m_mirrorMatrix * m_viewMatrix * matrix;
127 }
128 foreach (KoSubpath *subpath, subpaths()) {
129 foreach (KoPathPoint *point, *subpath) {
130 point->map(matrix);
131 }
132 }
133
134 const int handleCount = m_enhancedHandles.count();
136 for (int i = 0; i < handleCount; ++i) {
137 handles.append(matrix.map(m_enhancedHandles[i]->position()));
138 }
140
141 normalize();
142 }
143}
144
145void EnhancedPathShape::setSize(const QSizeF &newSize)
146{
147 // handle offset
149
150 // calculate scaling factors from viewbox size to shape size
151 qreal xScale = m_viewBound.width() == 0 ? 1 : newSize.width() / m_viewBound.width();
152 qreal yScale = m_viewBound.height() == 0 ? 1 : newSize.height() / m_viewBound.height();
153
154 // create view matrix, take mirroring into account
155 m_viewMatrix.reset();
156 m_viewMatrix.scale(xScale, yScale);
157
158 updatePath(newSize);
159}
160
162{
163 QPointF offset = KoParameterShape::normalize();
164
165 m_viewBoxOffset -= offset;
166
167 return offset;
168}
169
170QPointF EnhancedPathShape::shapeToViewbox(const QPointF &point) const
171{
172 return (m_mirrorMatrix * m_viewMatrix).inverted().map(point - m_viewBoxOffset);
173}
174
176{
177 const int handleCount = m_enhancedHandles.count();
179 for (int i = 0; i < handleCount; ++i) {
180 handles.append(m_enhancedHandles[i]->position());
181 }
183}
184
186{
187 return m_viewBox;
188}
189
190qreal EnhancedPathShape::evaluateReference(const QString &reference)
191{
192 if (reference.isEmpty()) {
193 return 0.0;
194 }
195
196 const char c = reference[0].toLatin1();
197
198 qreal res = 0.0;
199
200 switch (c) {
201 // referenced modifier
202 case '$': {
203 bool success = false;
204 int modifierIndex = reference.mid(1).toInt(&success);
205 res = m_modifiers.value(modifierIndex);
206 break;
207 }
208 // referenced formula
209 case '?': {
210 QString fname = reference.mid(1);
211 if (m_cacheResults && m_resultCache.contains(fname)) {
212 res = m_resultCache.value(fname);
213 } else {
214 FormulaStore::const_iterator formulaIt = m_formulae.constFind(fname);
215 if (formulaIt != m_formulae.constEnd()) {
216 EnhancedPathFormula *formula = formulaIt.value();
217 if (formula) {
218 res = formula->evaluate();
219 if (m_cacheResults) {
220 m_resultCache.insert(fname, res);
221 }
222 }
223 }
224 }
225 break;
226 }
227 // maybe an identifier ?
228 default:
229 EnhancedPathNamedParameter p(reference, this);
230 res = p.evaluate();
231 break;
232 }
233
234 return res;
235}
236
238{
239 bool ok = true;
240 qreal res = val.toDouble(&ok);
241 if (ok) {
242 return res;
243 }
244 return evaluateReference(val);
245}
246
247void EnhancedPathShape::modifyReference(const QString &reference, qreal value)
248{
249 if (reference.isEmpty()) {
250 return;
251 }
252
253 const char c = reference[0].toLatin1();
254
255 if (c == '$') {
256 bool success = false;
257 int modifierIndex = reference.mid(1).toInt(&success);
258 if (modifierIndex >= 0 && modifierIndex < m_modifiers.count()) {
259 m_modifiers[modifierIndex] = value;
260 }
261 }
262}
263
265{
266 Q_ASSERT(! text.isEmpty());
267
268 ParameterStore::const_iterator parameterIt = m_parameters.constFind(text);
269 if (parameterIt != m_parameters.constEnd()) {
270 return parameterIt.value();
271 } else {
273 const char c = text[0].toLatin1();
274 if (c == '$' || c == '?') {
276 } else {
277 bool success = false;
278 qreal constant = text.toDouble(&success);
279 if (success) {
280 parameter = new EnhancedPathConstantParameter(constant, this);
281 } else {
283 if (identifier != IdentifierUnknown) {
284 parameter = new EnhancedPathNamedParameter(identifier, this);
285 }
286 }
287 }
288
289 if (parameter) {
290 m_parameters[text] = parameter;
291 }
292
293 return parameter;
294 }
295}
296
297void EnhancedPathShape::addFormula(const QString &name, const QString &formula)
298{
299 if (name.isEmpty() || formula.isEmpty()) {
300 return;
301 }
302
303 m_formulae[name] = new EnhancedPathFormula(formula, this);
304}
305
306void EnhancedPathShape::addHandle(const QMap<QString, QVariant> &handle)
307{
308 if (handle.isEmpty()) {
309 return;
310 }
311
312 if (!handle.contains("draw:handle-position")) {
313 return;
314 }
315 QVariant position = handle.value("draw:handle-position");
316
317 QStringList tokens = position.toString().simplified().split(' ');
318 if (tokens.count() < 2) {
319 return;
320 }
321
322 EnhancedPathHandle *newHandle = new EnhancedPathHandle(this);
323 newHandle->setPosition(parameter(tokens[0]), parameter(tokens[1]));
324
325 // check if we have a polar handle
326 if (handle.contains("draw:handle-polar")) {
327 QVariant polar = handle.value("draw:handle-polar");
328 QStringList tokens = polar.toString().simplified().split(' ');
329 if (tokens.count() == 2) {
330 newHandle->setPolarCenter(parameter(tokens[0]), parameter(tokens[1]));
331
332 QVariant minRadius = handle.value("draw:handle-radius-range-minimum");
333 QVariant maxRadius = handle.value("draw:handle-radius-range-maximum");
334 if (minRadius.isValid() && maxRadius.isValid()) {
335 newHandle->setRadiusRange(parameter(minRadius.toString()), parameter(maxRadius.toString()));
336 }
337 }
338 } else {
339 QVariant minX = handle.value("draw:handle-range-x-minimum");
340 QVariant maxX = handle.value("draw:handle-range-x-maximum");
341 if (minX.isValid() && maxX.isValid()) {
342 newHandle->setRangeX(parameter(minX.toString()), parameter(maxX.toString()));
343 }
344
345 QVariant minY = handle.value("draw:handle-range-y-minimum");
346 QVariant maxY = handle.value("draw:handle-range-y-maximum");
347 if (minY.isValid() && maxY.isValid()) {
348 newHandle->setRangeY(parameter(minY.toString()), parameter(maxY.toString()));
349 }
350 }
351
352 m_enhancedHandles.append(newHandle);
353
355}
356
357void EnhancedPathShape::addModifiers(const QString &modifiers)
358{
359 if (modifiers.isEmpty()) {
360 return;
361 }
362
363 QStringList tokens = modifiers.simplified().split(' ');
364 int tokenCount = tokens.count();
365 for (int i = 0; i < tokenCount; ++i) {
366 m_modifiers.append(tokens[i].toDouble());
367 }
368}
369
370void EnhancedPathShape::addCommand(const QString &command)
371{
372 addCommand(command, true);
373}
374
375void EnhancedPathShape::addCommand(const QString &command, bool triggerUpdate)
376{
377 QString commandStr = command.simplified();
378 if (commandStr.isEmpty()) {
379 return;
380 }
381
382 // the first character is the command
383 EnhancedPathCommand *cmd = new EnhancedPathCommand(commandStr[0], this);
384
385 // strip command char
386 commandStr = commandStr.mid(1).simplified();
387
388 // now parse the command parameters
389 if (!commandStr.isEmpty()) {
390 QStringList tokens = commandStr.split(' ');
391 for (int i = 0; i < tokens.count(); ++i) {
392 cmd->addParameter(parameter(tokens[i]));
393 }
394 }
395 m_commands.append(cmd);
396
397 if (triggerUpdate) {
398 updatePath(size());
399 }
400}
401
402bool EnhancedPathShape::useStretchPoints(const QSizeF &size, qreal &scale)
403{
404 bool retval = false;
405 if (m_pathStretchPointX != -1 && m_pathStretchPointY != -1) {
406 qreal scaleX = size.width();
407 qreal scaleY = size.height();
408 if (qreal(m_viewBox.width()) / m_viewBox.height() < qreal(scaleX) / scaleY) {
409 qreal deltaX = (scaleX * m_viewBox.height()) / scaleY - m_viewBox.width();
410 foreach (KoSubpath *subpath, subpaths()) {
411 foreach (KoPathPoint *currPoint, *subpath) {
412 if (currPoint->point().x() >= m_pathStretchPointX &&
413 currPoint->controlPoint1().x() >= m_pathStretchPointX &&
414 currPoint->controlPoint2().x() >= m_pathStretchPointX) {
415 currPoint->setPoint(QPointF(currPoint->point().x() + deltaX, currPoint->point().y()));
416 currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x() + deltaX,
417 currPoint->controlPoint1().y()));
418 currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x() + deltaX,
419 currPoint->controlPoint2().y()));
420 retval = true;
421 }
422 }
423 }
424 scale = scaleY / m_viewBox.height();
425 } else if (qreal(m_viewBox.width()) / m_viewBox.height() > qreal(scaleX) / scaleY) {
426 qreal deltaY = (m_viewBox.width() * scaleY) / scaleX - m_viewBox.height();
427 foreach (KoSubpath *subpath, subpaths()) {
428 foreach (KoPathPoint *currPoint, *subpath) {
429 if (currPoint->point().y() >= m_pathStretchPointY &&
430 currPoint->controlPoint1().y() >= m_pathStretchPointY &&
431 currPoint->controlPoint2().y() >= m_pathStretchPointY) {
432 currPoint->setPoint(QPointF(currPoint->point().x(), currPoint->point().y() + deltaY));
433 currPoint->setControlPoint1(QPointF(currPoint->controlPoint1().x(),
434 currPoint->controlPoint1().y() + deltaY));
435 currPoint->setControlPoint2(QPointF(currPoint->controlPoint2().x(),
436 currPoint->controlPoint2().y() + deltaY));
437 retval = true;
438 }
439 }
440 }
441 scale = scaleX / m_viewBox.width();
442 }
443
445 }
446 return retval;
447}
448
449void EnhancedPathShape::parsePathData(const QString &data)
450{
451 if (data.isEmpty()) {
452 return;
453 }
454
455 int start = -1;
456 bool separator = true;
457 for (int i = 0; i < data.length(); ++i) {
458 QChar ch = data.at(i);
459 ushort uni_ch = ch.unicode();
460 if (separator && (uni_ch == 'M' || uni_ch == 'L'
461 || uni_ch == 'C' || uni_ch == 'Z'
462 || uni_ch == 'N' || uni_ch == 'F'
463 || uni_ch == 'S' || uni_ch == 'T'
464 || uni_ch == 'U' || uni_ch == 'A'
465 || uni_ch == 'B' || uni_ch == 'W'
466 || uni_ch == 'V' || uni_ch == 'X'
467 || uni_ch == 'Y' || uni_ch == 'Q')) {
468 if (start != -1) { // process last chars
469 addCommand(data.mid(start, i - start), false);
470 }
471 start = i;
472 }
473 separator = ch.isSpace();
474 }
475 if (start < data.length()) {
476 addCommand(data.mid(start));
477 }
478 if (start != -1) {
479 updatePath(size());
480 }
481}
482
483void EnhancedPathShape::setMirrorHorizontally(bool mirrorHorizontally)
484{
485 if (m_mirrorHorizontally != mirrorHorizontally) {
486 m_mirrorHorizontally = mirrorHorizontally;
487 updatePath(size());
488 }
489
490}
491
492void EnhancedPathShape::setMirrorVertically(bool mirrorVertically)
493{
494 if (m_mirrorVertically != mirrorVertically) {
495 m_mirrorVertically = mirrorVertically;
496 updatePath(size());
497 }
498}
499
501{
503
504 if (!shape || shape == this) {
505 if (type == ParentChanged || type == ParameterChanged) {
507 }
508 }
509}
510
512{
513 if (m_textArea.size() >= 4) {
514 QRectF r = m_viewBox;
519 r = m_viewMatrix.mapRect(r).translated(m_viewBoxOffset);
521 }
522}
523
525{
526 m_resultCache.clear();
527 m_cacheResults = enable;
528}
529
530void EnhancedPathShape::setPathStretchPointX(qreal pathStretchPointX)
531{
532 if (m_pathStretchPointX != pathStretchPointX) {
533 m_pathStretchPointX = pathStretchPointX;
534 }
535
536}
537
538void EnhancedPathShape::setPathStretchPointY(qreal pathStretchPointY)
539{
540 if (m_pathStretchPointY != pathStretchPointY) {
541 m_pathStretchPointY = pathStretchPointY;
542 }
543
544}
Identifier
the different possible identifiers, taken from the odf spec
@ IdentifierUnknown
unknown identifier
float value(const T *src, size_t ch)
const Params2D p
bool execute()
Executes the command on the specified path shape.
void addParameter(EnhancedPathParameter *parameter)
Adds a new parameter to the command.
A constant parameter, a fixed value (i.e. 5, 11.3, -7)
void setPolarCenter(EnhancedPathParameter *polarX, EnhancedPathParameter *polarY)
void changePosition(const QPointF &position)
void setPosition(EnhancedPathParameter *positionX, EnhancedPathParameter *positionY)
void setRangeX(EnhancedPathParameter *minX, EnhancedPathParameter *maxX)
void setRangeY(EnhancedPathParameter *minY, EnhancedPathParameter *maxY)
void setRadiusRange(EnhancedPathParameter *minRadius, EnhancedPathParameter *maxRadius)
A named parameter, one that refers to a variable of the path.
static Identifier identifierFromString(const QString &text)
Returns identifier type from given string.
The abstract parameter class.
A referencing parameter, one that references another formula or a modifier.
ParameterStore m_parameters
the shared parameters
bool m_cacheResults
indicates if result cache is enabled
QList< EnhancedPathHandle * > m_enhancedHandles
the handles for modifying the shape
bool m_mirrorHorizontally
whether or not the shape is to be mirrored horizontally before transforming it
void updateTextArea()
Updates the size and position of an optionally existing text-on-shape text area.
void setPathStretchPointX(qreal pathStretchPointX)
void addCommand(const QString &command)
Add command for instance "M 0 0".
void parsePathData(const QString &data)
parses the enhanced path data
void moveHandleAction(int handleId, const QPointF &point, Qt::KeyboardModifiers modifiers=Qt::NoModifier) override
Updates the internal state of a KoParameterShape.
void setMirrorVertically(bool mirrorVertically)
Sets if the shape is to be mirrored vertically before applying any other transformations.
void addHandle(const QMap< QString, QVariant > &handle)
Add a single handle with format: x y minX maxX minY maxY.
qreal evaluateConstantOrReference(const QString &val)
FormulaStore m_formulae
the formulae
void setPathStretchPointY(qreal pathStretchPointY)
EnhancedPathShape(const QRect &viewBox)
QPointF shapeToViewbox(const QPointF &point) const
Converts from shape coordinates to viewbox coordinates.
void setMirrorHorizontally(bool mirrorHorizontally)
Sets if the shape is to be mirrored horizontally before applying any other transformations.
qreal m_pathStretchPointX
draw:path-stretchpoint-x attribute
QRectF m_viewBound
the bounding box of the path in viewbox coordinates
QTransform m_mirrorMatrix
matrix to used for mirroring
QList< EnhancedPathCommand * > m_commands
the commands creating the outline
QTransform m_viewMatrix
matrix to convert from viewbox coordinates to shape coordinates
void setSize(const QSizeF &newSize) override
Resize the shape.
ModifierStore m_modifiers
the modifier values
void updatePath(const QSizeF &size) override
Update the path of the parameter shape.
bool m_mirrorVertically
whether or not the shape is to be mirrored vertically before transforming it
void addFormula(const QString &name, const QString &formula)
Add formula with given name and textual representation.
EnhancedPathParameter * parameter(const QString &text)
Returns parameter from given textual representation.
qreal evaluateReference(const QString &reference)
void modifyReference(const QString &reference, qreal value)
KoShape * cloneShape() const override
creates a deep copy of the shape or shape's subtree
QRect m_viewBox
the viewbox rectangle
void shapeChanged(ChangeType type, KoShape *shape=0) override
bool useStretchPoints(const QSizeF &size, qreal &scale)
QPointF normalize() override
Normalizes the path data.
void enableResultCache(bool enable)
Enables caching results.
QRect viewBox() const
Returns the viewbox of the enhanced path shape.
QHash< QString, qreal > m_resultCache
cache for intermediate results used when evaluating path
qreal m_pathStretchPointY
draw:path-stretchpoint-y attribute
void addModifiers(const QString &modifiers)
Add modifiers with format: modifier0 modifier1 modifier2 ...
QPointF normalize() override
Normalizes the path data.
QList< QPointF > handles
the handles that the user can grab and change
int handleCount() const
return the number of handles set on the shape
void setSize(const QSizeF &size) override
reimplemented from KoShape
void setHandles(const QList< QPointF > &handles)
bool isParametricShape() const
Check if object is a parametric shape.
A KoPathPoint represents a point in a path.
void setControlPoint1(const QPointF &point)
Set the control point 1.
QPointF point
void setControlPoint2(const QPointF &point)
Set the control point 2.
QPointF controlPoint1
void map(const QTransform &matrix)
apply matrix on the point
void setPoint(const QPointF &point)
alter the point
QPointF controlPoint2
const KoSubpathList & subpaths() const
QSizeF size() const override
reimplemented
void notifyPointsChanged()
QPainterPath outline() const override
reimplemented
void clear()
Removes all subpaths and their points from the path.
virtual void shapeChanged(ChangeType type, KoShape *shape=0)
Definition KoShape.cpp:1253
ChangeType
Used by shapeChanged() to select which change was made.
Definition KoShape.h:95
@ ParentChanged
used after a setParent()
Definition KoShape.h:103
@ ParameterChanged
the shapes parameter has changed (KoParameterShape only)
Definition KoShape.h:109
void scale(qreal sx, qreal sy)
Scale the shape using the zero-point which is the top-left corner.
Definition KoShape.cpp:237
QString name() const
Definition KoShape.cpp:1150
QPointF position() const
Get the position of the shape in pt.
Definition KoShape.cpp:825
void setPreferredTextRect(const QRectF &rect)
double toDouble(const quint8 *data, int channelpos)