Changeset View
Changeset View
Standalone View
Standalone View
libs/flake/KoMarker.cpp
Show All 22 Lines | |||||
23 | #include <KoXmlNS.h> | 23 | #include <KoXmlNS.h> | ||
24 | #include <KoGenStyle.h> | 24 | #include <KoGenStyle.h> | ||
25 | #include <KoGenStyles.h> | 25 | #include <KoGenStyles.h> | ||
26 | #include "KoPathShape.h" | 26 | #include "KoPathShape.h" | ||
27 | #include "KoPathShapeLoader.h" | 27 | #include "KoPathShapeLoader.h" | ||
28 | #include "KoShapeLoadingContext.h" | 28 | #include "KoShapeLoadingContext.h" | ||
29 | #include "KoShapeSavingContext.h" | 29 | #include "KoShapeSavingContext.h" | ||
30 | #include "KoOdfWorkaround.h" | 30 | #include "KoOdfWorkaround.h" | ||
31 | #include "KoShapePainter.h" | ||||
32 | #include "KoViewConverter.h" | ||||
33 | #include <KoShapeStroke.h> | ||||
34 | #include <KoGradientBackground.h> | ||||
35 | #include <KoColorBackground.h> | ||||
36 | | ||||
31 | 37 | | |||
32 | #include <QString> | 38 | #include <QString> | ||
33 | #include <QUrl> | 39 | #include <QUrl> | ||
34 | #include <QPainterPath> | 40 | #include <QPainterPath> | ||
41 | #include <QPainter> | ||||
42 | | ||||
43 | #include "kis_global.h" | ||||
44 | #include "kis_algebra_2d.h" | ||||
45 | | ||||
35 | 46 | | |||
36 | class Q_DECL_HIDDEN KoMarker::Private | 47 | class Q_DECL_HIDDEN KoMarker::Private | ||
37 | { | 48 | { | ||
38 | public: | 49 | public: | ||
39 | Private() | 50 | Private() | ||
51 | : coordinateSystem(StrokeWidth), | ||||
52 | referenceSize(3,3), | ||||
53 | hasAutoOrientation(false), | ||||
54 | explicitOrientation(0) | ||||
40 | {} | 55 | {} | ||
41 | 56 | | |||
57 | ~Private() { | ||||
58 | qDeleteAll(shapes); | ||||
59 | } | ||||
60 | | ||||
61 | bool operator==(const KoMarker::Private &other) const | ||||
62 | { | ||||
63 | // WARNING: comparison of shapes is extremely fuzzy! Don't | ||||
64 | // trust it in life-critical cases! | ||||
65 | | ||||
66 | return name == other.name && | ||||
67 | coordinateSystem == other.coordinateSystem && | ||||
68 | referencePoint == other.referencePoint && | ||||
69 | referenceSize == other.referenceSize && | ||||
70 | hasAutoOrientation == other.hasAutoOrientation && | ||||
71 | explicitOrientation == other.explicitOrientation && | ||||
72 | compareShapesTo(other.shapes); | ||||
73 | } | ||||
74 | | ||||
75 | Private(const Private &rhs) | ||||
76 | : name(rhs.name), | ||||
77 | coordinateSystem(rhs.coordinateSystem), | ||||
78 | referencePoint(rhs.referencePoint), | ||||
79 | referenceSize(rhs.referenceSize), | ||||
80 | hasAutoOrientation(rhs.hasAutoOrientation), | ||||
81 | explicitOrientation(rhs.explicitOrientation) | ||||
82 | { | ||||
83 | Q_FOREACH (KoShape *shape, rhs.shapes) { | ||||
84 | shapes << shape->cloneShape(); | ||||
85 | } | ||||
86 | } | ||||
87 | | ||||
42 | QString name; | 88 | QString name; | ||
43 | QString d; | 89 | MarkerCoordinateSystem coordinateSystem; | ||
44 | QPainterPath path; | 90 | QPointF referencePoint; | ||
45 | QRect viewBox; | 91 | QSizeF referenceSize; | ||
92 | | ||||
93 | bool hasAutoOrientation; | ||||
94 | qreal explicitOrientation; | ||||
95 | | ||||
96 | QList<KoShape*> shapes; | ||||
97 | QScopedPointer<KoShapePainter> shapePainter; | ||||
98 | | ||||
99 | bool compareShapesTo(const QList<KoShape*> other) const { | ||||
100 | if (shapes.size() != other.size()) return false; | ||||
101 | | ||||
102 | for (int i = 0; i < shapes.size(); i++) { | ||||
103 | if (shapes[i]->outline() != other[i]->outline() || | ||||
104 | shapes[i]->absoluteTransformation(0) != other[i]->absoluteTransformation(0)) { | ||||
105 | | ||||
106 | return false; | ||||
107 | } | ||||
108 | } | ||||
109 | | ||||
110 | return true; | ||||
111 | } | ||||
112 | | ||||
113 | QTransform markerTransform(qreal strokeWidth, qreal nodeAngle, const QPointF &pos = QPointF()) { | ||||
114 | const QTransform translate = QTransform::fromTranslate(-referencePoint.x(), -referencePoint.y()); | ||||
115 | | ||||
116 | QTransform t = translate; | ||||
117 | | ||||
118 | if (coordinateSystem == StrokeWidth) { | ||||
119 | t *= QTransform::fromScale(strokeWidth, strokeWidth); | ||||
120 | } | ||||
121 | | ||||
122 | const qreal angle = hasAutoOrientation ? nodeAngle : explicitOrientation; | ||||
123 | if (angle != 0.0) { | ||||
124 | QTransform r; | ||||
125 | r.rotateRadians(angle); | ||||
126 | t *= r; | ||||
127 | } | ||||
128 | | ||||
129 | t *= QTransform::fromTranslate(pos.x(), pos.y()); | ||||
130 | | ||||
131 | return t; | ||||
132 | } | ||||
46 | }; | 133 | }; | ||
47 | 134 | | |||
48 | KoMarker::KoMarker() | 135 | KoMarker::KoMarker() | ||
49 | : d(new Private()) | 136 | : d(new Private()) | ||
50 | { | 137 | { | ||
51 | } | 138 | } | ||
52 | 139 | | |||
53 | KoMarker::~KoMarker() | 140 | KoMarker::~KoMarker() | ||
54 | { | 141 | { | ||
55 | delete d; | 142 | delete d; | ||
56 | } | 143 | } | ||
57 | 144 | | |||
58 | bool KoMarker::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) | 145 | QString KoMarker::name() const | ||
146 | { | ||||
147 | return d->name; | ||||
148 | } | ||||
149 | | ||||
150 | KoMarker::KoMarker(const KoMarker &rhs) | ||||
151 | : QSharedData(rhs), | ||||
152 | d(new Private(*rhs.d)) | ||||
59 | { | 153 | { | ||
60 | Q_UNUSED(context); | 154 | } | ||
61 | // A shape uses draw:marker-end="Arrow" draw:marker-end-width="0.686cm" draw:marker-end-center="true" which marker and how the marker is used | | |||
62 | 155 | | |||
63 | //<draw:marker draw:name="Arrow" svg:viewBox="0 0 20 30" svg:d="m10 0-10 30h20z"/> | 156 | bool KoMarker::operator==(const KoMarker &other) const | ||
64 | //<draw:marker draw:name="Arrowheads_20_1" draw:display-name="Arrowheads 1" svg:viewBox="0 0 10 10" svg:d="m0 0h10v10h-10z"/> | 157 | { | ||
158 | return *d == *other.d; | ||||
159 | } | ||||
65 | 160 | | |||
66 | d->d =element.attributeNS(KoXmlNS::svg, "d"); | 161 | void KoMarker::setCoordinateSystem(KoMarker::MarkerCoordinateSystem value) | ||
67 | if (d->d.isEmpty()) { | 162 | { | ||
68 | return false; | 163 | d->coordinateSystem = value; | ||
69 | } | 164 | } | ||
70 | 165 | | |||
71 | #ifndef NWORKAROUND_ODF_BUGS | 166 | KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystem() const | ||
72 | KoOdfWorkaround::fixMarkerPath(d->d); | 167 | { | ||
73 | #endif | 168 | return d->coordinateSystem; | ||
74 | | ||||
75 | KoPathShape pathShape; | | |||
76 | KoPathShapeLoader loader(&pathShape); | | |||
77 | loader.parseSvg(d->d, true); | | |||
78 | | ||||
79 | d->path = pathShape.outline(); | | |||
80 | d->viewBox = KoPathShape::loadOdfViewbox(element); | | |||
81 | | ||||
82 | QString displayName(element.attributeNS(KoXmlNS::draw, "display-name")); | | |||
83 | if (displayName.isEmpty()) { | | |||
84 | displayName = element.attributeNS(KoXmlNS::draw, "name"); | | |||
85 | } | 169 | } | ||
86 | d->name = displayName; | 170 | | ||
87 | return true; | 171 | KoMarker::MarkerCoordinateSystem KoMarker::coordinateSystemFromString(const QString &value) | ||
172 | { | ||||
173 | MarkerCoordinateSystem result = StrokeWidth; | ||||
174 | | ||||
175 | if (value == "userSpaceOnUse") { | ||||
176 | result = UserSpaceOnUse; | ||||
177 | } | ||||
178 | | ||||
179 | return result; | ||||
88 | } | 180 | } | ||
89 | 181 | | |||
90 | QString KoMarker::saveOdf(KoShapeSavingContext &context) const | 182 | QString KoMarker::coordinateSystemToString(KoMarker::MarkerCoordinateSystem value) | ||
91 | { | 183 | { | ||
92 | KoGenStyle style(KoGenStyle::MarkerStyle); | 184 | return | ||
93 | style.addAttribute("draw:display-name", d->name); | 185 | value == StrokeWidth ? | ||
94 | style.addAttribute("svg:d", d->d); | 186 | "strokeWidth" : | ||
95 | const QString viewBox = QString::fromLatin1("%1 %2 %3 %4") | 187 | "userSpaceOnUse"; | ||
96 | .arg(d->viewBox.x()).arg(d->viewBox.y()) | | |||
97 | .arg(d->viewBox.width()).arg(d->viewBox.height()); | | |||
98 | style.addAttribute(QLatin1String("svg:viewBox"), viewBox); | | |||
99 | QString name = QString(QUrl::toPercentEncoding(d->name, "", " ")).replace('%', '_'); | | |||
100 | return context.mainStyles().insert(style, name, KoGenStyles::DontAddNumberToName); | | |||
101 | } | 188 | } | ||
102 | 189 | | |||
103 | QString KoMarker::name() const | 190 | void KoMarker::setReferencePoint(const QPointF &value) | ||
104 | { | 191 | { | ||
105 | return d->name; | 192 | d->referencePoint = value; | ||
106 | } | 193 | } | ||
107 | 194 | | |||
108 | QPainterPath KoMarker::path(qreal width) const | 195 | QPointF KoMarker::referencePoint() const | ||
109 | { | 196 | { | ||
110 | if (!d->viewBox.isValid() || width == 0) { | 197 | return d->referencePoint; | ||
111 | return QPainterPath(); | | |||
112 | } | 198 | } | ||
113 | 199 | | |||
114 | // TODO: currently the <min-x>, <min-y> properties of viewbox are ignored, why? OOo-compat? | 200 | void KoMarker::setReferenceSize(const QSizeF &size) | ||
115 | qreal height = width * d->viewBox.height() / d->viewBox.width(); | 201 | { | ||
202 | d->referenceSize = size; | ||||
203 | } | ||||
116 | 204 | | |||
117 | QTransform transform; | 205 | QSizeF KoMarker::referenceSize() const | ||
118 | transform.scale(width / d->viewBox.width(), height / d->viewBox.height()); | 206 | { | ||
119 | return transform.map(d->path); | 207 | return d->referenceSize; | ||
120 | } | 208 | } | ||
121 | 209 | | |||
122 | bool KoMarker::operator==(const KoMarker &other) const | 210 | bool KoMarker::hasAutoOtientation() const | ||
211 | { | ||||
212 | return d->hasAutoOrientation; | ||||
213 | } | ||||
214 | | ||||
215 | void KoMarker::setAutoOrientation(bool value) | ||||
216 | { | ||||
217 | d->hasAutoOrientation = value; | ||||
218 | } | ||||
219 | | ||||
220 | qreal KoMarker::explicitOrientation() const | ||||
221 | { | ||||
222 | return d->explicitOrientation; | ||||
223 | } | ||||
224 | | ||||
225 | void KoMarker::setExplicitOrientation(qreal value) | ||||
226 | { | ||||
227 | d->explicitOrientation = value; | ||||
228 | } | ||||
229 | | ||||
230 | void KoMarker::setShapes(const QList<KoShape *> &shapes) | ||||
231 | { | ||||
232 | d->shapes = shapes; | ||||
233 | | ||||
234 | if (d->shapePainter) { | ||||
235 | d->shapePainter->setShapes(shapes); | ||||
236 | } | ||||
237 | } | ||||
238 | | ||||
239 | QList<KoShape *> KoMarker::shapes() const | ||||
240 | { | ||||
241 | return d->shapes; | ||||
242 | } | ||||
243 | | ||||
244 | void KoMarker::paintAtPosition(QPainter *painter, const QPointF &pos, qreal strokeWidth, qreal nodeAngle) | ||||
245 | { | ||||
246 | QTransform oldTransform = painter->transform(); | ||||
247 | | ||||
248 | KoViewConverter converter; | ||||
249 | | ||||
250 | if (!d->shapePainter) { | ||||
251 | d->shapePainter.reset(new KoShapePainter()); | ||||
252 | d->shapePainter->setShapes(d->shapes); | ||||
253 | } | ||||
254 | | ||||
255 | painter->setTransform(d->markerTransform(strokeWidth, nodeAngle, pos), true); | ||||
256 | d->shapePainter->paint(*painter, converter); | ||||
257 | | ||||
258 | painter->setTransform(oldTransform); | ||||
259 | } | ||||
260 | | ||||
261 | qreal KoMarker::maxInset(qreal strokeWidth) const | ||||
262 | { | ||||
263 | QRectF shapesBounds = boundingRect(strokeWidth, 0.0); // normalized to 0,0 | ||||
264 | qreal result = 0.0; | ||||
265 | | ||||
266 | result = qMax(KisAlgebra2D::norm(shapesBounds.topLeft()), result); | ||||
267 | result = qMax(KisAlgebra2D::norm(shapesBounds.topRight()), result); | ||||
268 | result = qMax(KisAlgebra2D::norm(shapesBounds.bottomLeft()), result); | ||||
269 | result = qMax(KisAlgebra2D::norm(shapesBounds.bottomRight()), result); | ||||
270 | | ||||
271 | if (d->coordinateSystem == StrokeWidth) { | ||||
272 | result *= strokeWidth; | ||||
273 | } | ||||
274 | | ||||
275 | return result; | ||||
276 | } | ||||
277 | | ||||
278 | QRectF KoMarker::boundingRect(qreal strokeWidth, qreal nodeAngle) const | ||||
279 | { | ||||
280 | QRectF shapesBounds = KoShape::boundingRect(d->shapes); | ||||
281 | | ||||
282 | const QTransform t = d->markerTransform(strokeWidth, nodeAngle); | ||||
283 | | ||||
284 | if (!t.isIdentity()) { | ||||
285 | shapesBounds = t.mapRect(shapesBounds); | ||||
286 | } | ||||
287 | | ||||
288 | return shapesBounds; | ||||
289 | } | ||||
290 | | ||||
291 | QPainterPath KoMarker::outline(qreal strokeWidth, qreal nodeAngle) const | ||||
292 | { | ||||
293 | QPainterPath outline; | ||||
294 | Q_FOREACH (KoShape *shape, d->shapes) { | ||||
295 | outline |= shape->absoluteTransformation(0).map(shape->outline()); | ||||
296 | } | ||||
297 | | ||||
298 | const QTransform t = d->markerTransform(strokeWidth, nodeAngle); | ||||
299 | | ||||
300 | if (!t.isIdentity()) { | ||||
301 | outline = t.map(outline); | ||||
302 | } | ||||
303 | | ||||
304 | return outline; | ||||
305 | } | ||||
306 | | ||||
307 | void KoMarker::drawPreview(QPainter *painter, const QRectF &previewRect, const QPen &pen, KoFlake::MarkerPosition position) | ||||
123 | { | 308 | { | ||
124 | return (d->d == other.d->d && d->viewBox ==other.d->viewBox); | 309 | const QRectF outlineRect = outline(pen.widthF(), 0).boundingRect(); // normalized to 0,0 | ||
310 | QPointF marker; | ||||
311 | QPointF start; | ||||
312 | QPointF end; | ||||
313 | | ||||
314 | if (position == KoFlake::StartMarker) { | ||||
315 | marker = QPointF(-outlineRect.left() + previewRect.left(), previewRect.center().y()); | ||||
316 | start = marker; | ||||
317 | end = QPointF(previewRect.right(), start.y()); | ||||
318 | } else if (position == KoFlake::MidMarker) { | ||||
319 | start = QPointF(previewRect.left(), previewRect.center().y()); | ||||
320 | marker = QPointF(-outlineRect.center().x() + previewRect.center().x(), start.y()); | ||||
321 | end = QPointF(previewRect.right(), start.y()); | ||||
322 | } else if (position == KoFlake::EndMarker) { | ||||
323 | start = QPointF(previewRect.left(), previewRect.center().y()); | ||||
324 | marker = QPointF(-outlineRect.right() + previewRect.right(), start.y()); | ||||
325 | end = marker; | ||||
326 | } | ||||
327 | | ||||
328 | painter->save(); | ||||
329 | painter->setPen(pen); | ||||
330 | painter->setClipRect(previewRect); | ||||
331 | | ||||
332 | painter->drawLine(start, end); | ||||
333 | paintAtPosition(painter, marker, pen.widthF(), 0); | ||||
334 | | ||||
335 | painter->restore(); | ||||
336 | } | ||||
337 | | ||||
338 | void KoMarker::applyShapeStroke(KoShape *parentShape, KoShapeStroke *stroke, const QPointF &pos, qreal strokeWidth, qreal nodeAngle) | ||||
339 | { | ||||
340 | const QGradient *originalGradient = stroke->lineBrush().gradient(); | ||||
341 | | ||||
342 | if (!originalGradient) { | ||||
343 | QList<KoShape*> linearizedShapes = KoShape::linearizeSubtree(d->shapes); | ||||
344 | Q_FOREACH(KoShape *shape, linearizedShapes) { | ||||
345 | // update the stroke | ||||
346 | KoShapeStrokeSP shapeStroke = shape->stroke() ? | ||||
347 | qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) : | ||||
348 | KoShapeStrokeSP(); | ||||
349 | | ||||
350 | if (shapeStroke) { | ||||
351 | shapeStroke = toQShared(new KoShapeStroke(*shapeStroke)); | ||||
352 | | ||||
353 | shapeStroke->setLineBrush(QBrush()); | ||||
354 | shapeStroke->setColor(stroke->color()); | ||||
355 | | ||||
356 | shape->setStroke(shapeStroke); | ||||
357 | } | ||||
358 | | ||||
359 | // update the background | ||||
360 | if (shape->background()) { | ||||
361 | QSharedPointer<KoColorBackground> bg(new KoColorBackground(stroke->color())); | ||||
362 | shape->setBackground(bg); | ||||
363 | } | ||||
364 | } | ||||
365 | } else { | ||||
366 | QScopedPointer<QGradient> g(KoFlake::cloneGradient(originalGradient)); | ||||
367 | KIS_ASSERT_RECOVER_RETURN(g); | ||||
368 | | ||||
369 | const QTransform markerTransformInverted = | ||||
370 | d->markerTransform(strokeWidth, nodeAngle, pos).inverted(); | ||||
371 | | ||||
372 | QTransform gradientToUser; | ||||
373 | | ||||
374 | // Unwrap the gradient to work in global mode | ||||
375 | if (g->coordinateMode() == QGradient::ObjectBoundingMode) { | ||||
376 | QRectF boundingRect = | ||||
377 | parentShape ? | ||||
378 | parentShape->outline().boundingRect() : | ||||
379 | this->boundingRect(strokeWidth, nodeAngle); | ||||
380 | | ||||
381 | boundingRect = KisAlgebra2D::ensureRectNotSmaller(boundingRect, QSizeF(1.0, 1.0)); | ||||
382 | | ||||
383 | gradientToUser = QTransform(boundingRect.width(), 0, 0, boundingRect.height(), | ||||
384 | boundingRect.x(), boundingRect.y()); | ||||
385 | | ||||
386 | g->setCoordinateMode(QGradient::LogicalMode); | ||||
387 | } | ||||
388 | | ||||
389 | QList<KoShape*> linearizedShapes = KoShape::linearizeSubtree(d->shapes); | ||||
390 | Q_FOREACH(KoShape *shape, linearizedShapes) { | ||||
391 | // shape-unwinding transform | ||||
392 | QTransform t = gradientToUser * markerTransformInverted * shape->absoluteTransformation(0).inverted(); | ||||
393 | | ||||
394 | // update the stroke | ||||
395 | KoShapeStrokeSP shapeStroke = shape->stroke() ? | ||||
396 | qSharedPointerDynamicCast<KoShapeStroke>(shape->stroke()) : | ||||
397 | KoShapeStrokeSP(); | ||||
398 | | ||||
399 | if (shapeStroke) { | ||||
400 | shapeStroke = toQShared(new KoShapeStroke(*shapeStroke)); | ||||
401 | | ||||
402 | QBrush brush(*g); | ||||
403 | brush.setTransform(t); | ||||
404 | shapeStroke->setLineBrush(brush); | ||||
405 | shapeStroke->setColor(Qt::transparent); | ||||
406 | shape->setStroke(shapeStroke); | ||||
407 | } | ||||
408 | | ||||
409 | // update the background | ||||
410 | if (shape->background()) { | ||||
411 | | ||||
412 | QSharedPointer<KoGradientBackground> bg(new KoGradientBackground(KoFlake::cloneGradient(g.data()), t)); | ||||
413 | shape->setBackground(bg); | ||||
414 | } | ||||
415 | } | ||||
416 | } | ||||
125 | } | 417 | } |