LCOV - code coverage report
Current view: top level - src/importexport/imagesexport/internal - svggenerator.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 462 0
Test Date: 2026-11-05 04:07:02

            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 : }
        

Generated by: LCOV version 2.0-1