Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : */
22 :
23 : #include <QBuffer>
24 : #include <QFile>
25 : #include <QMimeDatabase>
26 : #include <QMimeType>
27 : #include <QPaintEngine>
28 : #include <QPainterPath>
29 : #include <QTextStream>
30 :
31 : #include <QStringConverter>
32 :
33 : #include "svggenerator.h"
34 : #include "types/bytearray.h"
35 : #include "engraving/dom/engravingitem.h"
36 : #include "engraving/dom/image.h"
37 : #include "engraving/dom/imageStore.h"
38 : #include "engraving/dom/mscore.h"
39 :
40 : #include "modularity/ioc.h"
41 : #include "global/iapplication.h"
42 :
43 : #include "log.h"
44 :
45 : ///////////////////////////////////////////////////////////////////////////////
46 : // FOR GRADIENT FUNCTIONALITY THAT IS NOT IMPLEMENTED (YET):
47 : //
48 : //#if QT_POINTER_SIZE == 8 // 64-bit versions
49 : //
50 : //static uint INTERPOLATE_PIXEL_256(uint x, uint a, uint y, uint b) {
51 : // quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a;
52 : // t += (((quint64(y)) | ((quint64(y)) << 24)) & 0x00ff00ff00ff00ff) * b;
53 : // t >>= 8;
54 : // t &= 0x00ff00ff00ff00ff;
55 : // return (uint(t)) | (uint(t >> 24));
56 : //}
57 : //
58 : //static uint PREMUL(uint x) {
59 : // uint a = x >> 24;
60 : // quint64 t = (((quint64(x)) | ((quint64(x)) << 24)) & 0x00ff00ff00ff00ff) * a;
61 : // t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8;
62 : // t &= 0x000000ff00ff00ff;
63 : // return (uint(t)) | (uint(t >> 24)) | (a << 24);
64 : //}
65 : //
66 : //#else // 32-bit versions
67 : //
68 : //static uint INTERPOLATE_PIXEL_256(uint x, uint a, uint y, uint b) {
69 : // uint t = (x & 0xff00ff) * a + (y & 0xff00ff) * b;
70 : // t >>= 8;
71 : // t &= 0xff00ff;
72 : //
73 : // x = ((x >> 8) & 0xff00ff) * a + ((y >> 8) & 0xff00ff) * b;
74 : // x &= 0xff00ff00;
75 : // x |= t;
76 : // return x;
77 : //}
78 : //
79 : //#if defined(Q_CC_RVCT)
80 : //# pragma push
81 : //# pragma arm
82 : //#endif
83 : //
84 : //#if defined(Q_CC_RVCT)
85 : //# pragma pop
86 : //#endif
87 : //
88 : //static uint PREMUL(uint x) {
89 : // uint a = x >> 24;
90 : // uint t = (x & 0xff00ff) * a;
91 : // t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8;
92 : // t &= 0xff00ff;
93 : //
94 : // x = ((x >> 8) & 0xff) * a;
95 : // x = (x + ((x >> 8) & 0xff) + 0x80);
96 : // x &= 0xff00;
97 : // x |= t | (a << 24);
98 : // return x;
99 : //}
100 : //#endif
101 : //
102 : /*#define INV_PREMUL(p) \
103 : (qAlpha(p) == 0 ? 0 : \
104 : ((qAlpha(p) << 24) \
105 : | (((255*qRed(p))/ qAlpha(p)) << 16) \
106 : | (((255*qGreen(p)) / qAlpha(p)) << 8) \
107 : | ((255*qBlue(p)) / qAlpha(p))))
108 : */
109 : ///////////////////////////////////////////////////////////////////////////////
110 :
111 0 : static void translate_color(const QColor& color, QString* color_string,
112 : QString* opacity_string)
113 : {
114 0 : Q_ASSERT(color_string);
115 0 : Q_ASSERT(opacity_string);
116 :
117 : *color_string
118 0 : =QString::fromLatin1("#%1%2%3")
119 0 : .arg(color.red(), 2, 16, QLatin1Char('0'))
120 0 : .arg(color.green(), 2, 16, QLatin1Char('0'))
121 0 : .arg(color.blue(), 2, 16, QLatin1Char('0'));
122 0 : *opacity_string = QString::number(color.alphaF());
123 0 : }
124 :
125 0 : static void translate_dashPattern(QVector<qreal> pattern, const qreal& width, QString* pattern_string)
126 : {
127 0 : Q_ASSERT(pattern_string);
128 :
129 : // Note that SVG operates in absolute lengths, whereas Qt uses a length/width ratio.
130 0 : foreach (qreal entry, pattern) {
131 0 : if (entry < 0) {
132 0 : entry = -entry;
133 : }
134 0 : *pattern_string += QString::fromLatin1("%1,").arg(entry * width);
135 0 : }
136 :
137 0 : pattern_string->chop(1);
138 0 : }
139 :
140 : // Gets the contents of the SVG class attribute, based on element type/name
141 0 : static QString getClass(const mu::engraving::EngravingItem* e)
142 : {
143 0 : QString eName;
144 :
145 : // Add element type as "class"
146 0 : if (e == NULL) {
147 0 : return eName; // e should never be null, this is extra-cautious
148 : }
149 0 : eName = e->typeName();
150 :
151 : // Future sub-typing code goes here
152 :
153 0 : return eName;
154 0 : }
155 :
156 : class SvgPaintEnginePrivate
157 : {
158 : muse::GlobalInject<muse::IApplication> application;
159 :
160 : public:
161 0 : SvgPaintEnginePrivate()
162 0 : {
163 0 : size = QSize();
164 0 : viewBox = QRectF();
165 0 : outputDevice = 0;
166 0 : resolution = mu::engraving::DPI;
167 :
168 0 : attributes.title = QLatin1String("MuseScore Studio SVG Document");
169 0 : attributes.description = QString("Generated by MuseScore Studio %1").arg(application()->version().toString());
170 : // UNUSED
171 : // attributes.font_family = QLatin1String("serif");
172 : // attributes.font_size = QLatin1String("10pt");
173 : // attributes.font_style = QLatin1String("normal");
174 : // attributes.font_weight = QLatin1String("normal");
175 : //
176 : // numGradients = 0;
177 0 : }
178 :
179 : QSize size;
180 : QRectF viewBox;
181 : QIODevice* outputDevice;
182 : QTextStream* stream;
183 : int resolution;
184 :
185 : QString header;
186 : // QString defs; // NEEDED FOR GRADIENTS
187 : QString body;
188 :
189 : QBrush brush;
190 : QPen pen;
191 : QTransform transform;
192 : // QFont font; // UNUSED
193 :
194 : // GRADIENTS NOT IMPLEMENTED (YET)
195 : // QString generateGradientName() {
196 : // ++numGradients;
197 : // currentGradientName = QString::fromLatin1("gradient%1").arg(numGradients);
198 : // return currentGradientName;
199 : // }
200 : //
201 : // QString currentGradientName;
202 : // int numGradients;
203 :
204 : struct _attributes {
205 : QString title;
206 : QString description;
207 : // UNUSED STRUCT MEMBERS:
208 : // QString font_weight;
209 : // QString font_size;
210 : // QString font_family;
211 : // QString font_style;
212 : // QString stroke, strokeOpacity;
213 : // QString dashPattern, dashOffset;
214 : // QString fill, fillOpacity;
215 : } attributes;
216 : };
217 :
218 0 : static inline QPaintEngine::PaintEngineFeatures svgEngineFeatures()
219 : {
220 0 : return QPaintEngine::PaintEngineFeatures(
221 : QPaintEngine::AllFeatures
222 : & ~QPaintEngine::PatternBrush
223 : & ~QPaintEngine::PerspectiveTransform
224 : & ~QPaintEngine::ConicalGradientFill
225 0 : & ~QPaintEngine::PorterDuff);
226 : }
227 :
228 : class SvgPaintEngine : public QPaintEngine
229 : {
230 : friend class SvgGenerator; // for setElement()
231 :
232 0 : Q_DECLARE_PRIVATE(SvgPaintEngine)
233 :
234 : private:
235 : QString stateString;
236 : QTextStream stateStream;
237 : SvgPaintEnginePrivate* d_ptr;
238 :
239 : // Qt translates everything. These help avoid SVG transform="translate()".
240 : qreal _dx { 0.0 };
241 : qreal _dy { 0.0 };
242 :
243 : protected:
244 : // The mu::engraving::EngravingItem being generated right now
245 : const mu::engraving::EngravingItem* _element = NULL;
246 :
247 : void writeImage(const QRectF& r, const QByteArray& imageData, const QString& mimeFormat);
248 :
249 : // SVG strings as constants
250 : #define SVG_SPACE ' '
251 : #define SVG_QUOTE "\""
252 : #define SVG_COMMA ","
253 : #define SVG_GT ">"
254 : #define SVG_MM "mm"
255 : #define SVG_NONE "none"
256 : #define SVG_EVENODD "evenodd"
257 : #define SVG_BUTT "butt"
258 : #define SVG_SQUARE "square"
259 : #define SVG_ROUND "round"
260 : #define SVG_MITER "miter"
261 : #define SVG_BEVEL "bevel"
262 : #define SVG_ONE "1"
263 : #define SVG_BLACK "#000000"
264 :
265 : #define SVG_BEGIN "<svg"
266 : #define SVG_END "</svg>"
267 :
268 : #define SVG_WIDTH " width=\""
269 : #define SVG_HEIGHT " height=\""
270 : #define SVG_VIEW_BOX " viewBox=\""
271 :
272 : #define SVG_X " x="
273 : #define SVG_Y " y="
274 :
275 : #define SVG_POINTS " points=\""
276 : #define SVG_D " d=\""
277 : #define SVG_MOVE 'M'
278 : #define SVG_LINE 'L'
279 : #define SVG_CURVE 'C'
280 : #define SVG_CLOSE 'Z'
281 :
282 : #define SVG_CLASS " class=\""
283 :
284 : #define SVG_ELEMENT_END "/>"
285 : #define SVG_RPAREN_QUOTE ")\""
286 :
287 : #define SVG_TITLE_BEGIN "<title>"
288 : #define SVG_TITLE_END "</title>"
289 : #define SVG_DESC_BEGIN "<desc>"
290 : #define SVG_DESC_END "</desc>"
291 :
292 : #define SVG_IMAGE "<image"
293 : #define SVG_PATH "<path"
294 : #define SVG_POLYLINE "<polyline"
295 :
296 : #define SVG_CLIP_PATH_BEGIN "<clipPath"
297 : #define SVG_CLIP_PATH_END "</clipPath>"
298 : #define SVG_MASK_BEGIN "<mask"
299 : #define SVG_MASK_END "</mask>"
300 : #define SVG_DEFS_BEGIN "<defs>"
301 : #define SVG_DEFS_END "</defs>"
302 : #define SVG_ID " id=\""
303 :
304 : #define SVG_PRESERVE_ASPECT " preserveAspectRatio=\""
305 :
306 : #define SVG_FILL " fill=\""
307 : #define SVG_STROKE " stroke=\""
308 : #define SVG_STROKE_WIDTH " stroke-width=\""
309 : #define SVG_STROKE_LINECAP " stroke-linecap=\""
310 : #define SVG_STROKE_LINEJOIN " stroke-linejoin=\""
311 : #define SVG_STROKE_DASHARRAY " stroke-dasharray=\""
312 : #define SVG_STROKE_DASHOFFSET " stroke-dashoffset=\""
313 : #define SVG_STROKE_MITERLIMIT " stroke-miterlimit=\""
314 :
315 : #define SVG_OPACITY " opacity=\""
316 : #define SVG_FILL_OPACITY " fill-opacity=\""
317 : #define SVG_STROKE_OPACITY " stroke-opacity=\""
318 :
319 : #define SVG_FONT_FAMILY " font-family=\""
320 : #define SVG_FONT_SIZE " font-size=\""
321 :
322 : #define SVG_FILL_RULE " fill-rule=\"evenodd\""
323 : #define SVG_CLIP_RULE " clip-rule=\"evenodd\""
324 : #define SVG_VECTOR_EFFECT " vector-effect=\"non-scaling-stroke\""
325 :
326 : #define SVG_MATRIX " transform=\"matrix("
327 :
328 : private:
329 : void drawPathData(const QPainterPath& p);
330 :
331 : int _curClipPathId = 0;
332 : bool _replaceClipPathWithMask = false;
333 : void defineClipPath(const QPainterPath& clipPath);
334 : void setClipPath();
335 :
336 : public:
337 0 : SvgPaintEngine()
338 0 : : QPaintEngine(svgEngineFeatures()),
339 0 : stateStream(&stateString)
340 : {
341 0 : d_ptr = new SvgPaintEnginePrivate;
342 0 : }
343 :
344 : bool begin(QPaintDevice* device) override;
345 : bool end() override;
346 :
347 : void updateState(const QPaintEngineState& state) override;
348 : void popGroup();
349 :
350 : void drawPath(const QPainterPath& path) override;
351 : void drawPixmap(const QRectF& r, const QPixmap& pm, const QRectF& sr) override;
352 : void drawPolygon(const QPointF* points, int pointCount, PolygonDrawMode mode) override;
353 0 : void drawImage(const QRectF& r, const QImage& pm, const QRectF& sr, Qt::ImageConversionFlags flags = Qt::AutoColor) override;
354 :
355 0 : QPaintEngine::Type type() const override { return QPaintEngine::SVG; }
356 :
357 0 : QSize size() const { return d_func()->size; }
358 0 : void setSize(const QSize& size)
359 : {
360 0 : Q_ASSERT(!isActive());
361 0 : d_func()->size = size;
362 0 : }
363 :
364 0 : QRectF viewBox() const { return d_func()->viewBox; }
365 0 : void setViewBox(const QRectF& viewBox)
366 : {
367 0 : Q_ASSERT(!isActive());
368 0 : d_func()->viewBox = viewBox;
369 0 : }
370 :
371 0 : QString documentTitle() const { return d_func()->attributes.title; }
372 0 : void setDocumentTitle(const QString& title)
373 : {
374 0 : d_func()->attributes.title = title;
375 0 : }
376 :
377 0 : QString documentDescription() const { return d_func()->attributes.description; }
378 0 : void setDocumentDescription(const QString& description)
379 : {
380 0 : d_func()->attributes.description = description;
381 0 : }
382 :
383 0 : QIODevice* outputDevice() const { return d_func()->outputDevice; }
384 0 : void setOutputDevice(QIODevice* device)
385 : {
386 0 : Q_ASSERT(!isActive());
387 0 : d_func()->outputDevice = device;
388 0 : }
389 :
390 0 : int resolution() { return d_func()->resolution; }
391 0 : void setResolution(int resolution)
392 : {
393 0 : Q_ASSERT(!isActive());
394 0 : d_func()->resolution = resolution;
395 0 : }
396 :
397 0 : void setReplaceClipPathWithMask(bool v) { _replaceClipPathWithMask = v; }
398 :
399 : ///////////////////////////////////////////////////////////////////////////////
400 : // UNUSED GRADIENT CODE:
401 : // void saveLinearGradientBrush(const QGradient *g)
402 : // {
403 : // QTextStream str(&d_func()->defs, QIODevice::Append);
404 : // const QLinearGradient *grad = static_cast<const QLinearGradient*>(g);
405 : // str << QLatin1String("<linearGradient ");
406 : // saveGradientUnits(str, g);
407 : // if (grad) {
408 : // str << QLatin1String("x1=\"") <<grad->start().x()<< QLatin1String("\" ")
409 : // << QLatin1String("y1=\"") <<grad->start().y()<< QLatin1String("\" ")
410 : // << QLatin1String("x2=\"") <<grad->finalStop().x() << QLatin1String("\" ")
411 : // << QLatin1String("y2=\"") <<grad->finalStop().y() << QLatin1String("\" ");
412 : // }
413 : //
414 : // str << QLatin1String("id=\"") << d_func()->generateGradientName() << QLatin1String("\">\n");
415 : // saveGradientStops(str, g);
416 : // str << QLatin1String("</linearGradient>") <<endl;
417 : // }
418 : // void saveRadialGradientBrush(const QGradient *g)
419 : // {
420 : // QTextStream str(&d_func()->defs, QIODevice::Append);
421 : // const QRadialGradient *grad = static_cast<const QRadialGradient*>(g);
422 : // str << QLatin1String("<radialGradient ");
423 : // saveGradientUnits(str, g);
424 : // if (grad) {
425 : // str << QLatin1String("cx=\"") <<grad->center().x()<< QLatin1String("\" ")
426 : // << QLatin1String("cy=\"") <<grad->center().y()<< QLatin1String("\" ")
427 : // << QLatin1String("r=\"") <<grad->radius() << QLatin1String("\" ")
428 : // << QLatin1String("fx=\"") <<grad->focalPoint().x() << QLatin1String("\" ")
429 : // << QLatin1String("fy=\"") <<grad->focalPoint().y() << QLatin1String("\" ");
430 : // }
431 : // str << QLatin1String("xml:id=\"") <<d_func()->generateGradientName()<< QLatin1String("\">\n");
432 : // saveGradientStops(str, g);
433 : // str << QLatin1String("</radialGradient>") << endl;
434 : // }
435 : // void saveConicalGradientBrush(const QGradient *)
436 : // {
437 : // LOGW("svg's don't support conical gradients!");
438 : // }
439 : //
440 : // void saveGradientStops(QTextStream &str, const QGradient *g) {
441 : // QGradientStops stops = g->stops();
442 : //
443 : // if (g->interpolationMode() == QGradient::ColorInterpolation) {
444 : // bool constantAlpha = true;
445 : // int alpha = stops.at(0).second.alpha();
446 : // for (int i = 1; i < stops.size(); ++i)
447 : // constantAlpha &= (stops.at(i).second.alpha() == alpha);
448 : //
449 : // if (!constantAlpha) {
450 : // const qreal spacing = qreal(0.02);
451 : // QGradientStops newStops;
452 : // QRgb fromColor = PREMUL(stops.at(0).second.rgba());
453 : // QRgb toColor;
454 : // for (int i = 0; i + 1 < stops.size(); ++i) {
455 : // int parts = qCeil((stops.at(i + 1).first - stops.at(i).first) / spacing);
456 : // newStops.append(stops.at(i));
457 : // toColor = PREMUL(stops.at(i + 1).second.rgba());
458 : //
459 : // if (parts > 1) {
460 : // qreal step = (stops.at(i + 1).first - stops.at(i).first) / parts;
461 : // for (int j = 1; j < parts; ++j) {
462 : // QRgb color = INV_PREMUL(INTERPOLATE_PIXEL_256(fromColor, 256 - 256 * j / parts, toColor, 256 * j / parts));
463 : // newStops.append(QGradientStop(stops.at(i).first + j * step, QColor::fromRgba(color)));
464 : // }
465 : // }
466 : // fromColor = toColor;
467 : // }
468 : // newStops.append(stops.back());
469 : // stops = newStops;
470 : // }
471 : // }
472 : //
473 : // foreach(QGradientStop stop, stops) {
474 : // QString color =
475 : // QString::fromLatin1("#%1%2%3")
476 : // .arg(stop.second.red(), 2, 16, QLatin1Char('0'))
477 : // .arg(stop.second.green(), 2, 16, QLatin1Char('0'))
478 : // .arg(stop.second.blue(), 2, 16, QLatin1Char('0'));
479 : // str << QLatin1String(" <stop offset=\"")<< stop.first << QLatin1String("\" ")
480 : // << QLatin1String("stop-color=\"") << color << QLatin1String("\" ")
481 : // << QLatin1String("stop-opacity=\"") << stop.second.alphaF() <<QLatin1String("\" />\n");
482 : // }
483 : // }
484 : //
485 : // void saveGradientUnits(QTextStream &str, const QGradient *gradient)
486 : // {
487 : // str << QLatin1String("gradientUnits=\"");
488 : // if (gradient && gradient->coordinateMode() == QGradient::ObjectBoundingMode)
489 : // str << QLatin1String("objectBoundingBox");
490 : // else
491 : // str << QLatin1String("userSpaceOnUse");
492 : // str << QLatin1String("\" ");
493 : // }
494 : // END UNUSED GRADIENT CODE
495 : ///////////////////////////////////////////////////////////////////////////////
496 :
497 0 : inline QTextStream& stream()
498 : {
499 0 : return *d_func()->stream;
500 : }
501 :
502 : //////////////////////////////
503 : // SvgPaintEngine::qpenToSVG()
504 : //////////////////////////////
505 0 : const QString qpenToSvg(const QPen& spen)
506 : {
507 0 : QString qs;
508 0 : QTextStream qts(&qs);
509 :
510 0 : QString color, colorOpacity;
511 :
512 : // Set stroke, stroke-dasharray, stroke-dashoffset attributes
513 0 : switch (spen.style()) {
514 0 : case Qt::NoPen:
515 0 : return qs; // Default value for stroke = "none" = Qt::NoPen = NOOP;
516 : break;
517 :
518 0 : case Qt::SolidLine:
519 : case Qt::DashLine:
520 : case Qt::DotLine:
521 : case Qt::DashDotLine:
522 : case Qt::DashDotDotLine:
523 : case Qt::CustomDashLine: {
524 : // These values are class variables because they are needed by
525 : // drawTextItem(). This is the fill color/opacity for text.
526 0 : translate_color(spen.color(), &color, &colorOpacity);
527 :
528 : // default stroke="none" is handled by case Qt::NoPen above
529 0 : qts << SVG_STROKE << color << SVG_QUOTE;
530 :
531 : // stroke-opacity is seldom used, usually set to default 1
532 0 : if (colorOpacity != SVG_ONE) {
533 0 : qts << SVG_STROKE_OPACITY << colorOpacity << SVG_QUOTE;
534 : }
535 :
536 : // If it's a solid line, were done for now
537 0 : if (spen.style() == Qt::SolidLine) {
538 0 : break;
539 : }
540 :
541 : // It's a dashed line
542 0 : qreal penWidth = spen.width() == 0 ? qreal(1) : spen.widthF();
543 :
544 0 : QString dashPattern, dashOffset;
545 0 : translate_dashPattern(spen.dashPattern(), penWidth, &dashPattern);
546 0 : dashOffset = QString::number(spen.dashOffset() * penWidth); // SVG uses absolute offset
547 :
548 0 : qts << SVG_STROKE_DASHARRAY << dashPattern << SVG_QUOTE;
549 0 : qts << SVG_STROKE_DASHOFFSET << dashOffset << SVG_QUOTE;
550 0 : break;
551 0 : }
552 0 : default:
553 0 : LOGW("Unsupported pen style");
554 0 : break;
555 : }
556 : // Set stroke-width attribute, unless it's zero or 1 (default is 1)
557 0 : if (spen.widthF() > 0 && spen.widthF() != 1) {
558 : // Formatting for vertical alignment of elements
559 0 : qts.setRealNumberPrecision(2); // with DPI=72 only 2 decimals necessary
560 0 : qts.setRealNumberNotation(QTextStream::FixedNotation);
561 0 : qts << SVG_STROKE_WIDTH << spen.widthF() << SVG_QUOTE;
562 0 : qts.setRealNumberNotation(QTextStream::SmartNotation);
563 : }
564 : // Set stroke-linecap attribute
565 0 : switch (spen.capStyle()) {
566 0 : case Qt::FlatCap:
567 : // This is the default stroke-linecap value
568 : //qts << SVG_STROKE_LINECAP << SVG_BUTT << SVG_QUOTE;
569 0 : break;
570 0 : case Qt::SquareCap:
571 0 : qts << SVG_STROKE_LINECAP << SVG_SQUARE << SVG_QUOTE;
572 0 : break;
573 0 : case Qt::RoundCap:
574 0 : qts << SVG_STROKE_LINECAP << SVG_ROUND << SVG_QUOTE;
575 0 : break;
576 0 : default:
577 0 : LOGW("Unhandled cap style");
578 0 : break;
579 : }
580 : // Set stroke-linejoin, stroke-miterlimit attributes
581 0 : switch (spen.joinStyle()) {
582 0 : case Qt::MiterJoin:
583 : case Qt::SvgMiterJoin:
584 0 : qts << SVG_STROKE_LINEJOIN << SVG_MITER << SVG_QUOTE
585 0 : << SVG_STROKE_MITERLIMIT << spen.miterLimit() << SVG_QUOTE;
586 0 : break;
587 0 : case Qt::BevelJoin:
588 0 : qts << SVG_STROKE_LINEJOIN << SVG_BEVEL << SVG_QUOTE;
589 0 : break;
590 0 : case Qt::RoundJoin:
591 0 : qts << SVG_STROKE_LINEJOIN << SVG_ROUND << SVG_QUOTE;
592 0 : break;
593 0 : default:
594 0 : LOGW("Unhandled join style");
595 0 : break;
596 : }
597 : // An uncommon, possibly non-existent in MuseScore, effect
598 0 : if (spen.isCosmetic()) {
599 0 : qts << SVG_VECTOR_EFFECT;
600 : }
601 :
602 0 : return qs;
603 0 : }
604 :
605 : /////////////////////////////////
606 : // SvgPaintEngine::qbrushToSVG()
607 : /////////////////////////////////
608 0 : const QString qbrushToSvg(const QBrush& sbrush)
609 : {
610 0 : QString qs;
611 0 : QTextStream qts(&qs);
612 :
613 0 : QString color, colorOpacity;
614 :
615 0 : switch (sbrush.style()) {
616 0 : case Qt::SolidPattern:
617 0 : translate_color(sbrush.color(), &color, &colorOpacity);
618 :
619 : // Default fill color is black
620 0 : if (color != SVG_BLACK) {
621 0 : qts << SVG_FILL << color << SVG_QUOTE;
622 : }
623 :
624 : // Default fill-opacity is 100%
625 0 : if (colorOpacity != SVG_ONE) {
626 0 : qts << SVG_FILL_OPACITY << colorOpacity << SVG_QUOTE;
627 : }
628 :
629 0 : break;
630 :
631 0 : case Qt::NoBrush:
632 0 : qts << SVG_FILL << SVG_NONE << SVG_QUOTE;
633 0 : break;
634 :
635 : ///////////////////////////////////////////////////////////////////////////////
636 : // OLD GRADIENT CODE: MuseScore does not support gradients (yet)
637 : //
638 : // case Qt::LinearGradientPattern:
639 : // saveLinearGradientBrush(sbrush.gradient());
640 : // d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
641 : // d_func()->attributes.fillOpacity = QString();
642 : // stateStream << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
643 : // break;
644 : // case Qt::RadialGradientPattern:
645 : // saveRadialGradientBrush(sbrush.gradient());
646 : // d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
647 : // d_func()->attributes.fillOpacity = QString();
648 : // stateStream << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
649 : // break;
650 : // case Qt::ConicalGradientPattern:
651 : // saveConicalGradientBrush(sbrush.gradient());
652 : // d_func()->attributes.fill = QString::fromLatin1("url(#%1)").arg(d_func()->currentGradientName);
653 : // d_func()->attributes.fillOpacity = QString();
654 : // stateStream << QLatin1String("fill=\"url(#") << d_func()->currentGradientName << QLatin1String(")\" ");
655 : // break;
656 : // case Qt::NoBrush:
657 : // stateStream << QLatin1String("fill=\"none\" ");
658 : // d_func()->attributes.fill = QLatin1String("none");
659 : // d_func()->attributes.fillOpacity = QString();
660 : // return;
661 : // break;
662 : ///////////////////////////////////////////////////////////////////////////////
663 :
664 0 : default:
665 0 : break;
666 : }
667 0 : return qs;
668 0 : }
669 :
670 : ///////////////////////////////////////////////////////////////////////////////
671 : // THIS FUNCTION IS NEVER USED and in the future should probably be replaced by
672 : // a drawTextItem() override instead
673 : //
674 : // void qfontToSvg(const QFont &sfont)
675 : // {
676 : // Q_D(SvgPaintEngine);
677 : //
678 : // d->font = sfont;
679 : //
680 : // if (d->font.pixelSize() == -1)
681 : // d->attributes.font_size = QString::number(d->font.pointSizeF() * d->resolution / 72);
682 : // else
683 : // d->attributes.font_size = QString::number(d->font.pixelSize());
684 : //
685 : // int svgWeight = d->font.weight();
686 : // switch (svgWeight) {
687 : // case QFont::Light:
688 : // svgWeight = 100;
689 : // break;
690 : // case QFont::Normal:
691 : // svgWeight = 400;
692 : // break;
693 : // case QFont::Bold:
694 : // svgWeight = 700;
695 : // break;
696 : // default:
697 : // svgWeight *= 10;
698 : // }
699 : //
700 : // d->attributes.font_weight = QString::number(svgWeight);
701 : // d->attributes.font_family = d->font.family();
702 : // d->attributes.font_style = d->font.italic() ? QLatin1String("italic") : QLatin1String("normal");
703 : //
704 : // stateStream << "font-family=\"" << d->attributes.font_family << "\" "
705 : // "font-size=\"" << d->attributes.font_size << "\" "
706 : // "font-weight=\"" << d->attributes.font_weight << "\" "
707 : // "font-style=\"" << d->attributes.font_style << "\" "
708 : // << endl;
709 : // }
710 : ///////////////////////////////////////////////////////////////////////////////
711 : };
712 :
713 : class SvgGeneratorPrivate
714 : {
715 : public:
716 : SvgPaintEngine* engine;
717 :
718 : uint owns_iodevice : 1;
719 : QString fileName;
720 : };
721 :
722 : /*!
723 : \class SvgGenerator
724 : \ingroup painting
725 : \since 4.3
726 : \brief The SvgGenerator class provides a paint device that is used to create SVG drawings.
727 : \reentrant
728 :
729 : This paint device represents a Scalable Vector Graphics (SVG) drawing. Like QPrinter, it is
730 : designed as a write-only device that generates output in a specific format.
731 :
732 : To write an SVG file, you first need to configure the output by setting the \l fileName
733 : or \l outputDevice properties. It is usually necessary to specify the size of the drawing
734 : by setting the \l size property, and in some cases where the drawing will be included in
735 : another, the \l viewBox property also needs to be set.
736 :
737 : \snippet examples/painting/svggenerator/window.cpp configure SVG generator
738 :
739 : Other meta-data can be specified by setting the \a title, \a description and \a resolution
740 : properties.
741 :
742 : As with other QPaintDevice subclasses, a QPainter object is used to paint onto an instance
743 : of this class:
744 :
745 : \snippet examples/painting/svggenerator/window.cpp begin painting
746 : \dots
747 : \snippet examples/painting/svggenerator/window.cpp end painting
748 :
749 : Painting is performed in the same way as for any other paint device. However,
750 : it is necessary to use the QPainter::begin() and \l{QPainter::}{end()} to
751 : explicitly begin and end painting on the device.
752 :
753 : The \l{SVG Generator Example} shows how the same painting commands can be used
754 : for painting a widget and writing an SVG file.
755 :
756 : \sa SvgRenderer, SvgWidget, {About SVG}
757 : */
758 :
759 : /*!
760 : Constructs a new generator.
761 : */
762 0 : SvgGenerator::SvgGenerator()
763 0 : : d_ptr(new SvgGeneratorPrivate)
764 : {
765 0 : Q_D(SvgGenerator);
766 :
767 0 : d->engine = new SvgPaintEngine;
768 0 : d->owns_iodevice = false;
769 0 : }
770 :
771 : /*!
772 : Destroys the generator.
773 : */
774 0 : SvgGenerator::~SvgGenerator()
775 : {
776 0 : Q_D(SvgGenerator);
777 0 : if (d->owns_iodevice) {
778 0 : delete d->engine->outputDevice();
779 : }
780 0 : delete d->engine;
781 0 : }
782 :
783 : /*!
784 : \property SvgGenerator::title
785 : \brief the title of the generated SVG drawing
786 : \since 4.5
787 : \sa description
788 : */
789 0 : QString SvgGenerator::title() const
790 : {
791 0 : Q_D(const SvgGenerator);
792 :
793 0 : return d->engine->documentTitle();
794 : }
795 :
796 0 : void SvgGenerator::setTitle(const QString& title)
797 : {
798 0 : Q_D(SvgGenerator);
799 :
800 0 : d->engine->setDocumentTitle(title);
801 0 : }
802 :
803 : /*!
804 : \property SvgGenerator::description
805 : \brief the description of the generated SVG drawing
806 : \since 4.5
807 : \sa title
808 : */
809 0 : QString SvgGenerator::description() const
810 : {
811 0 : Q_D(const SvgGenerator);
812 :
813 0 : return d->engine->documentDescription();
814 : }
815 :
816 0 : void SvgGenerator::setDescription(const QString& description)
817 : {
818 0 : Q_D(SvgGenerator);
819 :
820 0 : d->engine->setDocumentDescription(description);
821 0 : }
822 :
823 : /*!
824 : \property SvgGenerator::size
825 : \brief the size of the generated SVG drawing
826 : \since 4.5
827 :
828 : By default this property is set to \c{QSize(-1, -1)}, which
829 : indicates that the generator should not output the width and
830 : height attributes of the \c<svg> element.
831 :
832 : \note It is not possible to change this property while a
833 : QPainter is active on the generator.
834 :
835 : \sa viewBox, resolution
836 : */
837 0 : QSize SvgGenerator::size() const
838 : {
839 0 : Q_D(const SvgGenerator);
840 0 : return d->engine->size();
841 : }
842 :
843 0 : void SvgGenerator::setSize(const QSize& size)
844 : {
845 0 : Q_D(SvgGenerator);
846 0 : if (d->engine->isActive()) {
847 0 : LOGW("SvgGenerator::setSize(), cannot set size while SVG is being generated");
848 0 : return;
849 : }
850 0 : d->engine->setSize(size);
851 : }
852 :
853 : /*!
854 : \property SvgGenerator::viewBox
855 : \brief the viewBox of the generated SVG drawing
856 : \since 4.5
857 :
858 : By default this property is set to \c{QRect(0, 0, -1, -1)}, which
859 : indicates that the generator should not output the viewBox attribute
860 : of the \c<svg> element.
861 :
862 : \note It is not possible to change this property while a
863 : QPainter is active on the generator.
864 :
865 : \sa viewBox(), size, resolution
866 : */
867 0 : QRectF SvgGenerator::viewBoxF() const
868 : {
869 0 : Q_D(const SvgGenerator);
870 0 : return d->engine->viewBox();
871 : }
872 :
873 : /*!
874 : \since 4.5
875 :
876 : Returns viewBoxF().toRect().
877 :
878 : \sa viewBoxF()
879 : */
880 0 : QRect SvgGenerator::viewBox() const
881 : {
882 0 : Q_D(const SvgGenerator);
883 0 : return d->engine->viewBox().toRect();
884 : }
885 :
886 0 : void SvgGenerator::setViewBox(const QRectF& viewBox)
887 : {
888 0 : Q_D(SvgGenerator);
889 0 : if (d->engine->isActive()) {
890 0 : LOGW("SvgGenerator::setViewBox(), cannot set viewBox while SVG is being generated");
891 0 : return;
892 : }
893 0 : d->engine->setViewBox(viewBox);
894 : }
895 :
896 0 : void SvgGenerator::setViewBox(const QRect& viewBox)
897 : {
898 0 : setViewBox(QRectF(viewBox));
899 0 : }
900 :
901 : /*!
902 : \property SvgGenerator::fileName
903 : \brief the target filename for the generated SVG drawing
904 : \since 4.5
905 :
906 : \sa outputDevice
907 : */
908 0 : QString SvgGenerator::fileName() const
909 : {
910 0 : Q_D(const SvgGenerator);
911 0 : return d->fileName;
912 : }
913 :
914 0 : void SvgGenerator::setFileName(const QString& fileName)
915 : {
916 0 : Q_D(SvgGenerator);
917 0 : if (d->engine->isActive()) {
918 0 : LOGW("SvgGenerator::setFileName(), cannot set file name while SVG is being generated");
919 0 : return;
920 : }
921 :
922 0 : if (d->owns_iodevice) {
923 0 : delete d->engine->outputDevice();
924 : }
925 :
926 0 : d->owns_iodevice = true;
927 :
928 0 : d->fileName = fileName;
929 0 : QFile* file = new QFile(fileName);
930 0 : d->engine->setOutputDevice(file);
931 : }
932 :
933 : /*!
934 : \property SvgGenerator::outputDevice
935 : \brief the output device for the generated SVG drawing
936 : \since 4.5
937 :
938 : If both output device and file name are specified, the output device
939 : will have precedence.
940 :
941 : \sa fileName
942 : */
943 0 : QIODevice* SvgGenerator::outputDevice() const
944 : {
945 0 : Q_D(const SvgGenerator);
946 0 : return d->engine->outputDevice();
947 : }
948 :
949 0 : void SvgGenerator::setOutputDevice(QIODevice* outputDevice)
950 : {
951 0 : Q_D(SvgGenerator);
952 0 : if (d->engine->isActive()) {
953 0 : LOGW("SvgGenerator::setOutputDevice(), cannot set output device while SVG is being generated");
954 0 : return;
955 : }
956 0 : d->owns_iodevice = false;
957 0 : d->engine->setOutputDevice(outputDevice);
958 0 : d->fileName = QString();
959 : }
960 :
961 : /*!
962 : \property SvgGenerator::resolution
963 : \brief the resolution of the generated output
964 : \since 4.5
965 :
966 : The resolution is specified in dots per inch, and is used to
967 : calculate the physical size of an SVG drawing.
968 :
969 : \sa size, viewBox
970 : */
971 0 : int SvgGenerator::resolution() const
972 : {
973 0 : Q_D(const SvgGenerator);
974 0 : return d->engine->resolution();
975 : }
976 :
977 0 : void SvgGenerator::setResolution(int dpi)
978 : {
979 0 : Q_D(SvgGenerator);
980 0 : d->engine->setResolution(dpi);
981 0 : }
982 :
983 : /*!
984 : Returns the paint engine used to render graphics to be converted to SVG
985 : format information.
986 : */
987 0 : QPaintEngine* SvgGenerator::paintEngine() const
988 : {
989 0 : Q_D(const SvgGenerator);
990 0 : return d->engine;
991 : }
992 :
993 : /*!
994 : \reimp
995 : */
996 0 : int SvgGenerator::metric(QPaintDevice::PaintDeviceMetric metric) const
997 : {
998 0 : Q_D(const SvgGenerator);
999 0 : switch (metric) {
1000 0 : case QPaintDevice::PdmDepth:
1001 0 : return 32;
1002 0 : case QPaintDevice::PdmWidth:
1003 0 : return d->engine->size().width();
1004 0 : case QPaintDevice::PdmHeight:
1005 0 : return d->engine->size().height();
1006 0 : case QPaintDevice::PdmDpiX:
1007 0 : return d->engine->resolution();
1008 0 : case QPaintDevice::PdmDpiY:
1009 0 : return d->engine->resolution();
1010 0 : case QPaintDevice::PdmHeightMM:
1011 0 : return qRound(d->engine->size().height() / mu::engraving::DPMM);
1012 0 : case QPaintDevice::PdmWidthMM:
1013 0 : return qRound(d->engine->size().width() / mu::engraving::DPMM);
1014 0 : case QPaintDevice::PdmNumColors:
1015 0 : return 0xffffffff;
1016 0 : case QPaintDevice::PdmPhysicalDpiX:
1017 0 : return d->engine->resolution();
1018 0 : case QPaintDevice::PdmPhysicalDpiY:
1019 0 : return d->engine->resolution();
1020 0 : case QPaintDevice::PdmDevicePixelRatio:
1021 : case QPaintDevice::PdmDevicePixelRatioScaled:
1022 0 : return 1;
1023 0 : default:
1024 0 : LOGD("SvgGenerator::metric(), unhandled metric %d\n", metric);
1025 0 : break;
1026 : }
1027 0 : return 0;
1028 : }
1029 :
1030 : /*!
1031 : setElement() function
1032 : Sets the _element variable in SvgPaintEngine.
1033 : Called by saveSVG() in mscore/file.cpp.
1034 : */
1035 0 : void SvgGenerator::setElement(const mu::engraving::EngravingItem* e)
1036 : {
1037 0 : static_cast<SvgPaintEngine*>(paintEngine())->_element = e;
1038 0 : }
1039 :
1040 0 : void SvgGenerator::setReplaceClipPathWithMask(bool v)
1041 : {
1042 0 : static_cast<SvgPaintEngine*>(paintEngine())->setReplaceClipPathWithMask(v);
1043 0 : }
1044 :
1045 : /*****************************************************************************
1046 : * class SvgPaintEngine
1047 : */
1048 :
1049 0 : bool SvgPaintEngine::begin(QPaintDevice*)
1050 : {
1051 0 : Q_D(SvgPaintEngine);
1052 :
1053 : // Check for errors
1054 0 : if (!d->outputDevice) {
1055 0 : LOGW("SvgPaintEngine::begin(), no output device");
1056 0 : return false;
1057 : }
1058 0 : if (!d->outputDevice->isOpen()) {
1059 0 : if (!d->outputDevice->open(QIODevice::WriteOnly | QIODevice::Text)) {
1060 0 : LOGW("SvgPaintEngine::begin(), could not open output device: '%s'",
1061 0 : qPrintable(d->outputDevice->errorString()));
1062 0 : return false;
1063 : }
1064 0 : } else if (!d->outputDevice->isWritable()) {
1065 0 : LOGW("SvgPaintEngine::begin(), could not write to read-only output device: '%s'",
1066 0 : qPrintable(d->outputDevice->errorString()));
1067 0 : return false;
1068 : }
1069 :
1070 : // Stream the headers
1071 0 : d->stream = new QTextStream(&d->header);
1072 0 : stream() << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" << Qt::endl << SVG_BEGIN;
1073 0 : if (d->viewBox.isValid()) {
1074 : // viewBox has floating point values, size width/height is integer
1075 0 : stream() << SVG_WIDTH << d->viewBox.width() / mu::engraving::DPMM << SVG_MM << SVG_QUOTE
1076 0 : << SVG_HEIGHT << d->viewBox.height() / mu::engraving::DPMM << SVG_MM << SVG_QUOTE;
1077 :
1078 0 : stream() << SVG_VIEW_BOX << d->viewBox.left()
1079 0 : << SVG_SPACE << d->viewBox.top()
1080 0 : << SVG_SPACE << d->viewBox.width()
1081 0 : << SVG_SPACE << d->viewBox.height() << SVG_QUOTE << Qt::endl;
1082 : }
1083 0 : stream() << " xmlns=\"http://www.w3.org/2000/svg\""
1084 : " xmlns:xlink=\"http://www.w3.org/1999/xlink\""
1085 0 : " version=\"1.2\" baseProfile=\"full\">" << Qt::endl;
1086 0 : if (!d->attributes.title.isEmpty()) {
1087 0 : stream() << SVG_TITLE_BEGIN << d->attributes.title.toHtmlEscaped() << SVG_TITLE_END << Qt::endl;
1088 : }
1089 0 : if (!d->attributes.description.isEmpty()) {
1090 0 : stream() << SVG_DESC_BEGIN << d->attributes.description.toHtmlEscaped() << SVG_DESC_END << Qt::endl;
1091 : }
1092 :
1093 : // <defs> is currently empty. It's necessary for gradients.
1094 : // d->stream->setString(&d->defs);
1095 : // *d->stream << "<defs>\n";
1096 :
1097 : // Point the stream at the body string, for other functions to populate
1098 0 : d->stream->setString(&d->body);
1099 0 : return true;
1100 : }
1101 :
1102 0 : bool SvgPaintEngine::end()
1103 : {
1104 0 : Q_D(SvgPaintEngine);
1105 :
1106 : // <defs> is currently empty
1107 : // d->stream->setString(&d->defs);
1108 : // stream() << "</defs>\n";
1109 :
1110 : // Point the stream at the real output device (the .svg file)
1111 0 : d->stream->setDevice(d->outputDevice);
1112 0 : d->stream->setEncoding(QStringConverter::Utf8);
1113 :
1114 : // Stream our strings out to the device, in order
1115 0 : stream() << d->header;
1116 : // stream() << d->defs;
1117 0 : stream() << d->body;
1118 0 : stream() << SVG_END << Qt::endl;
1119 :
1120 0 : delete d->stream;
1121 0 : return true;
1122 : }
1123 :
1124 0 : void SvgPaintEngine::drawPixmap(const QRectF& r, const QPixmap& pm,
1125 : const QRectF& sr)
1126 : {
1127 0 : drawImage(r, pm.toImage(), sr);
1128 0 : }
1129 :
1130 0 : void SvgPaintEngine::drawImage(const QRectF& r, const QImage& image,
1131 : const QRectF& sr, Qt::ImageConversionFlags flags)
1132 : {
1133 : Q_UNUSED(sr);
1134 : Q_UNUSED(flags);
1135 :
1136 0 : QByteArray data;
1137 0 : QBuffer buffer(&data);
1138 :
1139 0 : buffer.open(QBuffer::ReadWrite);
1140 0 : image.save(&buffer, "PNG");
1141 0 : buffer.close();
1142 :
1143 : // check whether we can just use the original raster image
1144 : // to reduce the resulting file size
1145 0 : if (_element && _element->isImage()) {
1146 : static const QList<QString> allowedImageTypes {
1147 : "image/jpeg",
1148 : "image/png"
1149 0 : };
1150 :
1151 0 : const mu::engraving::Image* img = mu::engraving::toImage(_element);
1152 0 : const mu::engraving::ImageStoreItem* storeItem = img->storeItem(); // holds the original image file content
1153 0 : if (img->imageType() == mu::engraving::ImageType::RASTER && storeItem) {
1154 0 : const muse::ByteArray& imgBa = storeItem->buffer();
1155 0 : QByteArray imgData = imgBa.toQByteArrayNoCopy();
1156 0 : const QMimeType type = QMimeDatabase().mimeTypeForData(imgData);
1157 0 : if (type.isValid() && allowedImageTypes.contains(type.name())
1158 0 : && imgData.size() < data.size()) {
1159 : // this really can save space, save the original image
1160 0 : writeImage(r, imgData, type.name());
1161 0 : return;
1162 : }
1163 0 : }
1164 : }
1165 :
1166 0 : writeImage(r, data, "image/png");
1167 0 : }
1168 :
1169 0 : void SvgPaintEngine::writeImage(const QRectF& r, const QByteArray& imageData, const QString& mimeFormat)
1170 : {
1171 0 : stream() << SVG_IMAGE << stateString
1172 0 : << SVG_X << SVG_QUOTE << r.x() + _dx << SVG_QUOTE
1173 0 : << SVG_Y << SVG_QUOTE << r.y() + _dy << SVG_QUOTE
1174 0 : << SVG_WIDTH << r.width() << SVG_QUOTE
1175 0 : << SVG_HEIGHT << r.height() << SVG_QUOTE
1176 0 : << SVG_PRESERVE_ASPECT << SVG_NONE << SVG_QUOTE;
1177 :
1178 0 : stream() << " xlink:href=\"data:" << mimeFormat << ";base64,"
1179 0 : << imageData.toBase64() << SVG_QUOTE << SVG_ELEMENT_END << Qt::endl;
1180 0 : }
1181 :
1182 0 : void SvgPaintEngine::updateState(const QPaintEngineState& s)
1183 : {
1184 : // Always start fresh
1185 0 : stateString.clear();
1186 :
1187 : // stateString = Attribute Settings
1188 :
1189 : // SVG class attribute, based on mu::engraving::ElementType
1190 0 : stateStream << SVG_CLASS << getClass(_element) << SVG_QUOTE;
1191 :
1192 : // Brush and Pen attributes
1193 0 : stateStream << qbrushToSvg(s.brush());
1194 0 : stateStream << qpenToSvg(s.pen());
1195 :
1196 : // TBD: "opacity" attribute: Is it ever used?
1197 : // Or is opacity determined by fill-opacity & stroke-opacity instead?
1198 : // PLUS: qFuzzyIsNull() is not officially supported in Qt.
1199 : // Should probably use QFuzzyCompare() instead.
1200 0 : if (!qFuzzyIsNull(s.opacity() - 1)) {
1201 0 : stateStream << SVG_OPACITY << s.opacity() << SVG_QUOTE;
1202 : }
1203 :
1204 : // Translations, SVG transform="translate()", are handled separately from
1205 : // other transformations such as rotation. Qt translates everything, but
1206 : // other transformations do occur, and must be handled here.
1207 0 : QTransform t = s.painter()->transform();
1208 :
1209 : // Tablature Note Text:
1210 : // m11 and m22 have floating point flotsam, for example: 1.000000629
1211 : // Both values should be == integer 1, because no scaling is intended.
1212 : // So round them to three decimal places, as MuseScore does elsewhere.
1213 0 : const qreal m11 = qRound(t.m11() * 1000) / 1000.0;
1214 0 : const qreal m22 = qRound(t.m22() * 1000) / 1000.0;
1215 :
1216 0 : if (m11 == 1 && m22 == 1 // No scaling
1217 0 : && t.m12() == t.m21()) { // No rotation, etc.
1218 : // No transformation except translation
1219 0 : _dx = t.m31();
1220 0 : _dy = t.m32();
1221 : } else {
1222 : // Other transformations are more straightforward with a full matrix
1223 0 : _dx = 0;
1224 0 : _dy = 0;
1225 0 : stateStream << SVG_MATRIX << t.m11() << SVG_COMMA
1226 0 : << t.m12() << SVG_COMMA
1227 0 : << t.m21() << SVG_COMMA
1228 0 : << t.m22() << SVG_COMMA
1229 0 : << t.m31() << SVG_COMMA
1230 0 : << t.m32() << SVG_RPAREN_QUOTE;
1231 : }
1232 0 : }
1233 :
1234 0 : void SvgPaintEngine::drawPath(const QPainterPath& p)
1235 : {
1236 0 : QPainterPath clipPath = painter()->clipPath();
1237 0 : if (!clipPath.isEmpty()) {
1238 0 : defineClipPath(clipPath);
1239 : }
1240 :
1241 0 : stream() << SVG_PATH << stateString;
1242 :
1243 : // fill-rule is here because UpdateState() doesn't have a QPainterPath arg
1244 : // Majority of <path>s use the default value: fill-rule="nonzero"
1245 0 : if (p.fillRule() == Qt::OddEvenFill) {
1246 0 : stream() << SVG_FILL_RULE;
1247 : }
1248 :
1249 0 : drawPathData(p);
1250 :
1251 0 : if (!clipPath.isEmpty()) {
1252 0 : setClipPath();
1253 : }
1254 :
1255 0 : stream() << SVG_ELEMENT_END << Qt::endl;
1256 0 : }
1257 :
1258 0 : void SvgPaintEngine::drawPathData(const QPainterPath& p)
1259 : {
1260 : // Path data
1261 0 : stream() << SVG_D;
1262 0 : int subpathStart = 0;
1263 0 : for (int i = 0; i < p.elementCount(); ++i) {
1264 0 : const QPainterPath::Element& e = p.elementAt(i);
1265 0 : qreal x = e.x + _dx;
1266 0 : qreal y = e.y + _dy;
1267 0 : switch (e.type) {
1268 0 : case QPainterPath::MoveToElement:
1269 0 : subpathStart = i;
1270 0 : stream() << SVG_MOVE << x << SVG_COMMA << y;
1271 0 : break;
1272 0 : case QPainterPath::LineToElement: {
1273 : // closeSubpath() is stored as a line back to the start point; write it as Z so the corner is joined
1274 0 : const bool endsSubpath = i == p.elementCount() - 1 || p.elementAt(i + 1).isMoveTo();
1275 0 : if (endsSubpath && i - subpathStart > 1 && QPointF(e) == QPointF(p.elementAt(subpathStart))) {
1276 0 : stream() << SVG_CLOSE;
1277 : } else {
1278 0 : stream() << SVG_LINE << x << SVG_COMMA << y;
1279 : }
1280 0 : break;
1281 : }
1282 0 : case QPainterPath::CurveToElement:
1283 0 : stream() << SVG_CURVE << x << SVG_COMMA << y;
1284 0 : ++i;
1285 0 : while (i < p.elementCount()) {
1286 0 : const QPainterPath::Element& ee = p.elementAt(i);
1287 0 : if (ee.type == QPainterPath::CurveToDataElement) {
1288 0 : stream() << SVG_SPACE << ee.x + _dx
1289 0 : << SVG_COMMA << ee.y + _dy;
1290 0 : ++i;
1291 : } else {
1292 0 : --i;
1293 0 : break;
1294 : }
1295 : }
1296 0 : break;
1297 0 : default:
1298 0 : break;
1299 : }
1300 0 : if (i <= p.elementCount() - 1) {
1301 0 : stream() << SVG_SPACE;
1302 : }
1303 : }
1304 0 : stream() << SVG_QUOTE;
1305 0 : }
1306 :
1307 0 : void SvgPaintEngine::defineClipPath(const QPainterPath& clipPath)
1308 : {
1309 0 : _curClipPathId++;
1310 :
1311 0 : if (_replaceClipPathWithMask) {
1312 0 : stream() << SVG_DEFS_BEGIN << SVG_SPACE << SVG_MASK_BEGIN << SVG_ID << _curClipPathId << SVG_QUOTE << SVG_GT << SVG_SPACE;
1313 : } else {
1314 0 : stream() << SVG_DEFS_BEGIN << SVG_SPACE << SVG_CLIP_PATH_BEGIN << SVG_ID << _curClipPathId << SVG_QUOTE << SVG_GT << SVG_SPACE;
1315 : }
1316 :
1317 0 : stream() << SVG_PATH;
1318 0 : drawPathData(clipPath);
1319 :
1320 0 : if (_replaceClipPathWithMask) {
1321 0 : stream() << SVG_FILL_RULE << SVG_FILL << "white\"" << SVG_ELEMENT_END << SVG_MASK_END << SVG_DEFS_END << Qt::endl;
1322 : } else {
1323 0 : stream() << SVG_CLIP_RULE << SVG_ELEMENT_END << SVG_CLIP_PATH_END << SVG_DEFS_END << Qt::endl;
1324 : }
1325 0 : }
1326 :
1327 0 : void SvgPaintEngine::setClipPath()
1328 : {
1329 0 : if (_replaceClipPathWithMask) {
1330 0 : stream() << " mask=\"url(#" << _curClipPathId << ")\"";
1331 : } else {
1332 0 : stream() << " clip-path=\"url(#" << _curClipPathId << ")\"";
1333 : }
1334 0 : }
1335 :
1336 0 : void SvgPaintEngine::drawPolygon(const QPointF* points, int pointCount,
1337 : PolygonDrawMode mode)
1338 : {
1339 0 : Q_ASSERT(pointCount >= 2);
1340 :
1341 0 : QPainterPath path(points[0]);
1342 0 : for (int i=1; i < pointCount; ++i) {
1343 0 : path.lineTo(points[i]);
1344 : }
1345 :
1346 0 : if (mode == PolylineMode) {
1347 0 : QPainterPath clipPath = painter()->clipPath();
1348 0 : if (!clipPath.isEmpty()) {
1349 0 : defineClipPath(clipPath);
1350 : }
1351 :
1352 : // fixes draw polyline
1353 0 : painter()->setBrush(Qt::NoBrush);
1354 0 : updateState(*this->state);
1355 :
1356 0 : stream() << SVG_POLYLINE << stateString
1357 0 : << SVG_POINTS;
1358 0 : for (int i = 0; i < pointCount; ++i) {
1359 0 : const QPointF& pt = points[i];
1360 0 : stream() << pt.x() + _dx << SVG_COMMA << pt.y() + _dy;
1361 0 : if (i != pointCount - 1) {
1362 0 : stream() << SVG_SPACE;
1363 : }
1364 : }
1365 0 : stream() << SVG_QUOTE << SVG_SPACE;
1366 :
1367 0 : if (!clipPath.isEmpty()) {
1368 0 : setClipPath();
1369 : }
1370 :
1371 0 : stream() << SVG_ELEMENT_END << Qt::endl;
1372 0 : } else {
1373 0 : path.closeSubpath();
1374 0 : drawPath(path);
1375 : }
1376 0 : }
|