1
/* This file is part of the KDE project
2
* Copyright (C) 2007,2010 Jan Hambrecht <jaham@gmx.net>
3
* Copyright (C) 2009-2010 Thomas Zander <zander@kde.org>
4
* Copyright (C) 2010 Carlos Licea <carlos@kdab.com>
5
* Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
6
* Contact: Suresh Chande suresh.chande@nokia.com
8
* This library is free software; you can redistribute it and/or
9
* modify it under the terms of the GNU Library General Public
10
* License as published by the Free Software Foundation; either
11
* version 2 of the License, or (at your option) any later version.
13
* This library is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16
* Library General Public License for more details.
18
* You should have received a copy of the GNU Library General Public License
19
* along with this library; see the file COPYING.LIB. If not, write to
20
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21
* Boston, MA 02110-1301, USA.
24
#include "EnhancedPathShape.h"
25
#include "EnhancedPathCommand.h"
26
#include "EnhancedPathParameter.h"
27
#include "EnhancedPathHandle.h"
28
#include "EnhancedPathFormula.h"
31
#include <KoXmlWriter.h>
32
#include <KoXmlReader.h>
33
#include <KoShapeSavingContext.h>
35
#include <KoOdfWorkaround.h>
36
#include <KoPathPoint.h>
38
EnhancedPathShape::EnhancedPathShape(const QRectF &viewBox)
39
: m_viewBox(viewBox), m_viewBoxOffset(0.0, 0.0), m_mirrorVertically(false), m_mirrorHorizontally(false)
43
EnhancedPathShape::~EnhancedPathShape()
48
void EnhancedPathShape::reset()
50
qDeleteAll(m_commands);
52
qDeleteAll(m_enhancedHandles);
53
m_enhancedHandles.clear();
54
setHandles(QList<QPointF>());
55
qDeleteAll(m_formulae);
57
qDeleteAll(m_parameters);
61
m_viewBoxOffset = QPointF();
65
void EnhancedPathShape::moveHandleAction(int handleId, const QPointF & point, Qt::KeyboardModifiers modifiers)
68
EnhancedPathHandle *handle = m_enhancedHandles[ handleId ];
70
handle->changePosition(shapeToViewbox(point));
74
void EnhancedPathShape::updatePath(const QSizeF &)
78
foreach (EnhancedPathCommand *cmd, m_commands)
81
m_viewBound = outline().boundingRect();
84
matrix.translate(m_viewBoxOffset.x(), m_viewBoxOffset.y());
85
matrix = m_viewMatrix * matrix;
87
KoSubpathList::const_iterator pathIt(m_subpaths.constBegin());
88
for (; pathIt != m_subpaths.constEnd(); ++pathIt) {
89
KoSubpath::const_iterator it((*pathIt)->constBegin());
90
for (; it != (*pathIt)->constEnd(); ++it) {
94
const int handleCount = m_enhancedHandles.count();
95
QList<QPointF> handles;
96
for (int i = 0; i < handleCount; ++i)
97
handles.append(matrix.map(m_enhancedHandles[i]->position()));
103
void EnhancedPathShape::setSize(const QSizeF &newSize)
105
KoParameterShape::setSize(newSize);
107
// calculate scaling factors from viewbox size to shape size
108
qreal xScale = newSize.width()/m_viewBound.width();
109
qreal yScale = newSize.height()/m_viewBound.height();
111
// create view matrix, take mirroring into account
112
m_viewMatrix.reset();
113
m_viewMatrix.translate(m_viewBound.center().x(), m_viewBound.center().y());
114
m_viewMatrix.scale(m_mirrorHorizontally ? -xScale : xScale, m_mirrorVertically ? -yScale : yScale);
115
m_viewMatrix.translate(-m_viewBound.center().x(), -m_viewBound.center().y());
120
QPointF EnhancedPathShape::normalize()
122
QPointF offset = KoParameterShape::normalize();
124
m_viewBoxOffset -= offset;
129
QPointF EnhancedPathShape::shapeToViewbox(const QPointF &point) const
131
return m_viewMatrix.inverted().map( point-m_viewBoxOffset );
134
void EnhancedPathShape::evaluateHandles()
136
const int handleCount = m_enhancedHandles.count();
137
QList<QPointF> handles;
138
for (int i = 0; i < handleCount; ++i)
139
handles.append(m_enhancedHandles[i]->position());
143
QRectF EnhancedPathShape::viewBox() const
148
qreal EnhancedPathShape::evaluateReference(const QString &reference)
150
if (reference.isEmpty())
153
QChar c = reference[0];
157
switch(c.toAscii()) {
158
// referenced modifier
160
bool success = false;
161
int modifierIndex = reference.mid(1).toInt(&success);
162
res = m_modifiers.value(modifierIndex);
165
// referenced formula
167
QString fname = reference.mid(1);
168
FormulaStore::const_iterator formulaIt = m_formulae.constFind(fname);
169
if (formulaIt != m_formulae.constEnd())
171
EnhancedPathFormula * formula = formulaIt.value();
173
res = formula->evaluate();
177
// maybe an identifier ?
179
EnhancedPathNamedParameter p(reference, this);
187
void EnhancedPathShape::modifyReference(const QString &reference, qreal value)
189
if (reference.isEmpty())
192
QChar c = reference[0];
194
if (c.toAscii() == '$') {
195
bool success = false;
196
int modifierIndex = reference.mid(1).toInt(&success);
197
if (modifierIndex >= 0 && modifierIndex < m_modifiers.count())
198
m_modifiers[modifierIndex] = value;
202
EnhancedPathParameter * EnhancedPathShape::parameter(const QString & text)
204
Q_ASSERT(! text.isEmpty());
206
ParameterStore::const_iterator parameterIt = m_parameters.constFind(text);
207
if (parameterIt != m_parameters.constEnd()) {
208
return parameterIt.value();
210
EnhancedPathParameter *parameter = 0;
212
if (c.toAscii() == '$' || c.toAscii() == '?') {
213
parameter = new EnhancedPathReferenceParameter(text, this);
216
bool success = false;
217
qreal constant = text.toDouble(&success);
219
parameter = new EnhancedPathConstantParameter(constant, this);
221
Identifier identifier = EnhancedPathNamedParameter::identifierFromString(text);
222
if (identifier != IdentifierUnknown)
223
parameter = new EnhancedPathNamedParameter(identifier, this);
228
m_parameters[text] = parameter;
234
void EnhancedPathShape::addFormula(const QString &name, const QString &formula)
236
if (name.isEmpty() || formula.isEmpty())
239
m_formulae[name] = new EnhancedPathFormula(formula, this);
242
void EnhancedPathShape::addHandle(const QMap<QString,QVariant> &handle)
244
if (handle.isEmpty())
247
if (! handle.contains("draw:handle-position"))
249
QVariant position = handle.value("draw:handle-position");
251
QStringList tokens = position.toString().simplified().split(' ');
252
if (tokens.count() < 2)
255
EnhancedPathHandle *newHandle = new EnhancedPathHandle(this);
256
newHandle->setPosition(parameter(tokens[0]), parameter(tokens[1]));
258
// check if we have a polar handle
259
if (handle.contains("draw:handle-polar")) {
260
QVariant polar = handle.value("draw:handle-polar");
261
QStringList tokens = polar.toString().simplified().split(' ');
262
if (tokens.count() == 2) {
263
newHandle->setPolarCenter(parameter(tokens[0]), parameter(tokens[1]));
265
QVariant minRadius = handle.value("draw:handle-radius-range-minimum");
266
QVariant maxRadius = handle.value("draw:handle-radius-range-maximum");
267
if (minRadius.isValid() && maxRadius.isValid())
268
newHandle->setRadiusRange(parameter(minRadius.toString()), parameter(maxRadius.toString()));
271
QVariant minX = handle.value("draw:handle-range-x-minimum");
272
QVariant maxX = handle.value("draw:handle-range-x-maximum");
273
if (minX.isValid() && maxX.isValid())
274
newHandle->setRangeX(parameter(minX.toString()), parameter(maxX.toString()));
276
QVariant minY = handle.value("draw:handle-range-y-minimum");
277
QVariant maxY = handle.value("draw:handle-range-y-maximum");
278
if (minY.isValid() && maxY.isValid())
279
newHandle->setRangeY(parameter(minY.toString()), parameter(maxY.toString()));
282
m_enhancedHandles.append(newHandle);
287
void EnhancedPathShape::addModifiers(const QString &modifiers)
289
if (modifiers.isEmpty())
292
QStringList tokens = modifiers.simplified().split(' ');
293
int tokenCount = tokens.count();
294
for (int i = 0; i < tokenCount; ++i)
295
m_modifiers.append(tokens[i].toDouble());
298
void EnhancedPathShape::addCommand(const QString &command)
300
addCommand(command, true);
303
void EnhancedPathShape::addCommand(const QString &command, bool triggerUpdate)
305
QString commandStr = command.simplified();
306
if (commandStr.isEmpty())
309
// the first character is the command
310
EnhancedPathCommand *cmd = new EnhancedPathCommand(commandStr[0], this);
312
// strip command char
313
commandStr = commandStr.mid(1).simplified();
315
// now parse the command parameters
316
if (!commandStr.isEmpty()) {
317
QStringList tokens = commandStr.split(' ');
318
for (int i = 0; i < tokens.count(); ++i)
319
cmd->addParameter(parameter(tokens[i]));
321
m_commands.append(cmd);
327
void EnhancedPathShape::saveOdf(KoShapeSavingContext &context) const
329
if (isParametricShape()) {
330
context.xmlWriter().startElement("draw:custom-shape");
331
saveOdfAttributes(context, OdfAllAttributes&~OdfSize);
333
// save the right size so that when loading we fit the viewbox
334
// to the right size without getting any wrong scaling
335
// -> calculate the right size from the current size/viewbound ratio
336
const QSizeF currentSize = outline().boundingRect().size();
337
context.xmlWriter().addAttributePt("svg:width", m_viewBox.width()*currentSize.width()/m_viewBound.width());
338
context.xmlWriter().addAttributePt("svg:height", m_viewBox.height()*currentSize.height()/m_viewBound.height());
340
context.xmlWriter().startElement("draw:enhanced-geometry");
341
context.xmlWriter().addAttribute("svg:viewBox", QString("%1 %2 %3 %4").arg(m_viewBox.x()).arg(m_viewBox.y()).arg(m_viewBox.width()).arg(m_viewBox.height()));
344
foreach (qreal modifier, m_modifiers)
345
modifiers += QString::number(modifier) + ' ';
346
context.xmlWriter().addAttribute("draw:modifiers", modifiers.trimmed());
349
foreach (EnhancedPathCommand * c, m_commands)
350
path += c->toString() + ' ';
351
context.xmlWriter().addAttribute("draw:enhanced-path", path.trimmed());
353
FormulaStore::const_iterator i = m_formulae.constBegin();
354
for (; i != m_formulae.constEnd(); ++i) {
355
context.xmlWriter().startElement("draw:equation");
356
context.xmlWriter().addAttribute("draw:name", i.key());
357
context.xmlWriter().addAttribute("draw:formula", i.value()->toString());
358
context.xmlWriter().endElement(); // draw:equation
361
foreach (EnhancedPathHandle * handle, m_enhancedHandles)
362
handle->saveOdf(context);
364
context.xmlWriter().endElement(); // draw:enhanced-geometry
365
saveOdfCommonChildElements(context);
366
context.xmlWriter().endElement(); // draw:custom-shape
368
if (m_mirrorHorizontally) {
369
context.xmlWriter().addAttribute("draw:mirror-horizontal", "true");
371
if (m_mirrorVertically) {
372
context.xmlWriter().addAttribute("draw:mirror-vertical", "true");
375
KoPathShape::saveOdf(context);
379
bool EnhancedPathShape::loadOdf(const KoXmlElement & element, KoShapeLoadingContext &context)
384
forEachElement(child, element) {
385
if (child.localName() == "enhanced-geometry" && child.namespaceURI() == KoXmlNS::draw) {
387
QRectF viewBox = loadOdfViewbox(child);
388
if (! viewBox.isEmpty())
391
// load the modifiers
392
QString modifiers = child.attributeNS(KoXmlNS::draw, "modifiers", "");
393
if (! modifiers.isEmpty()) {
394
addModifiers(modifiers);
397
KoXmlElement grandChild;
398
forEachElement(grandChild, child) {
399
if (grandChild.namespaceURI() != KoXmlNS::draw)
401
if (grandChild.localName() == "equation") {
402
QString name = grandChild.attributeNS(KoXmlNS::draw, "name");
403
QString formula = grandChild.attributeNS(KoXmlNS::draw, "formula");
404
addFormula(name, formula);
405
} else if (grandChild.localName() == "handle") {
406
EnhancedPathHandle * handle = new EnhancedPathHandle(this);
407
if (handle->loadOdf(grandChild, context)) {
408
m_enhancedHandles.append(handle);
416
// load the enhanced path data
417
QString path = child.attributeNS(KoXmlNS::draw, "enhanced-path", "");
418
#ifndef NWORKAROUND_ODF_BUGS
419
KoOdfWorkaround::fixEnhancedPath(path, child, context);
421
if (!path.isEmpty()) {
425
setMirrorHorizontally( child.attributeNS(KoXmlNS::draw, "mirror-horizontal") == "true");
426
setMirrorVertically( child.attributeNS(KoXmlNS::draw, "mirror-vertical") == "true");
431
size.setWidth(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "width", QString())));
432
size.setHeight(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "height", QString())));
433
// the viewbox is to be fitted into the size of the shape, so before setting
434
// the size we just loaded // we set the viewbox to be the basis to calculate
435
// the viewbox matrix from
436
m_viewBound = m_viewBox;
440
pos.setX(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "x", QString())));
441
pos.setY(KoUnit::parseValue(element.attributeNS(KoXmlNS::svg, "y", QString())));
444
loadOdfAttributes(element, context, OdfMandatories | OdfTransformation | OdfAdditionalAttributes | OdfCommonChildElements);
449
void EnhancedPathShape::parsePathData(const QString &data)
455
bool separator = true;
456
for (int i = 0; i < data.length(); ++i) {
457
QChar ch = data.at(i);
458
if (separator && (ch.unicode() == 'M' || ch.unicode() == 'L'
459
|| ch.unicode() == 'C' || ch.unicode() == 'Z'
460
|| ch.unicode() == 'N' || ch.unicode() == 'F'
461
|| ch.unicode() == 'S' || ch.unicode() == 'T'
462
|| ch.unicode() == 'U' || ch.unicode() == 'A'
463
|| ch.unicode() == 'B' || ch.unicode() == 'W'
464
|| ch.unicode() == 'V' || ch.unicode() == 'X'
465
|| ch.unicode() == 'Y' || ch.unicode() == 'Q')) {
466
if (start != -1) { // process last chars
467
addCommand(data.mid(start, i - start));
471
separator = ch.isSpace();
473
if (start < data.length())
474
addCommand(data.mid(start));
479
void EnhancedPathShape::setMirrorHorizontally(bool mirrorHorizontally)
481
if( m_mirrorHorizontally != mirrorHorizontally) {
482
m_mirrorHorizontally = mirrorHorizontally;
486
void EnhancedPathShape::setMirrorVertically(bool mirrorVertically)
488
if( m_mirrorVertically != mirrorVertically) {
489
m_mirrorVertically = mirrorVertically;