changeset 29519:78f42413e20f

octave-svgconvert: Don't use Qt's SVG module (bug #59546) * acinclude.m4: Don't require QtSvg * octave-qsvghandler.h (toDouble, isDigit, parseNumbersArray, pathArcSegment, pathArc, parseTransformationMatrix, parsePathDataFast): New file. Set of static functions extracted from Qt-5.12 source code to build a QPainterPath from the "d" attribute of a "path" element. * module.mk: Include new header file in build system. * octave-svgconvert.cc (pdfpainter): Revert changes. (draw): Put back. Modify "g" element handler to account for potential "path", "text" or "use" children elements. Remove "tspan" element handler, which is no more needed, and refactor "text" element handler. Implement new "path" and "use" element handlers. (main): The DPI argument is no more needed. Just comment it out to eventually use it if we implement raster outputs.
author Pantxo Diribarne <pantxo.diribarne@gmail.com>
date Wed, 07 Apr 2021 22:21:36 +0200
parents e8a0d4a91fb4
children ddd25345734d
files m4/acinclude.m4 src/module.mk src/octave-qsvghandler.h src/octave-svgconvert.cc
diffstat 4 files changed, 1208 insertions(+), 95 deletions(-) [+]
line wrap: on
line diff
--- a/m4/acinclude.m4	Fri Apr 09 09:38:40 2021 -0400
+++ b/m4/acinclude.m4	Wed Apr 07 22:21:36 2021 +0200
@@ -2031,7 +2031,7 @@
   case "$qt_version" in
     5)
       QT_OPENGL_MODULE="Qt5OpenGL"
-      QT_MODULES="Qt5Core Qt5Gui Qt5Help Qt5Network Qt5PrintSupport Qt5Svg Qt5Xml"
+      QT_MODULES="Qt5Core Qt5Gui Qt5Help Qt5Network Qt5PrintSupport Qt5Xml"
     ;;
     *)
       AC_MSG_ERROR([Unrecognized Qt version $qt_version])
--- a/src/module.mk	Fri Apr 09 09:38:40 2021 -0400
+++ b/src/module.mk	Wed Apr 07 22:21:36 2021 +0200
@@ -38,6 +38,7 @@
 
 noinst_HEADERS += \
   %reldir%/display-available.h \
+  %reldir%/octave-qsvghandler.h \
   %reldir%/shared-fcns.h
 
 OCTAVE_VERSION_LINKS += %reldir%/octave-cli-$(version)$(EXEEXT)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/octave-qsvghandler.h	Wed Apr 07 22:21:36 2021 +0200
@@ -0,0 +1,755 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt SVG module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+// --------------------------------------------------------------------------
+// Build a QPainterPath from the "d" attribute of a path element
+// These functions are extracted from Qt-5.12 sources (qtsvghandler.cpp)
+// Modifications:
+//   * use static_cast<qreal> to avoid old style cast warning.
+// --------------------------------------------------------------------------
+
+#include <QPainterPath>
+
+static inline bool isDigit(ushort ch)
+{
+    static quint16 magic = 0x3ff;
+    return ((ch >> 4) == 3) && (magic >> (ch & 15));
+}
+
+static qreal toDouble(const QChar *&str)
+{
+    const int maxLen = 255;//technically doubles can go til 308+ but whatever
+    char temp[maxLen+1];
+    int pos = 0;
+
+    if (*str == QLatin1Char('-')) {
+        temp[pos++] = '-';
+        ++str;
+    } else if (*str == QLatin1Char('+')) {
+        ++str;
+    }
+    while (isDigit(str->unicode()) && pos < maxLen) {
+        temp[pos++] = str->toLatin1();
+        ++str;
+    }
+    if (*str == QLatin1Char('.') && pos < maxLen) {
+        temp[pos++] = '.';
+        ++str;
+    }
+    while (isDigit(str->unicode()) && pos < maxLen) {
+        temp[pos++] = str->toLatin1();
+        ++str;
+    }
+    bool exponent = false;
+    if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) {
+        exponent = true;
+        temp[pos++] = 'e';
+        ++str;
+        if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) {
+            temp[pos++] = str->toLatin1();
+            ++str;
+        }
+        while (isDigit(str->unicode()) && pos < maxLen) {
+            temp[pos++] = str->toLatin1();
+            ++str;
+        }
+    }
+
+    temp[pos] = '\0';
+
+    qreal val;
+    if (!exponent && pos < 10) {
+        int ival = 0;
+        const char *t = temp;
+        bool neg = false;
+        if(*t == '-') {
+            neg = true;
+            ++t;
+        }
+        while(*t && *t != '.') {
+            ival *= 10;
+            ival += (*t) - '0';
+            ++t;
+        }
+        if(*t == '.') {
+            ++t;
+            int div = 1;
+            while(*t) {
+                ival *= 10;
+                ival += (*t) - '0';
+                div *= 10;
+                ++t;
+            }
+            val = static_cast<qreal> (ival)/static_cast<qreal> (div);
+        } else {
+            val = ival;
+        }
+        if (neg)
+            val = -val;
+    } else {
+        val = QByteArray::fromRawData(temp, pos).toDouble();
+    }
+    return val;
+
+}
+
+static inline void parseNumbersArray(const QChar *&str, QVarLengthArray<qreal, 8> &points)
+{
+    while (str->isSpace())
+        ++str;
+    while (isDigit(str->unicode()) ||
+           *str == QLatin1Char('-') || *str == QLatin1Char('+') ||
+           *str == QLatin1Char('.')) {
+
+        points.append(toDouble(str));
+
+        while (str->isSpace())
+            ++str;
+        if (*str == QLatin1Char(','))
+            ++str;
+
+        //eat the rest of space
+        while (str->isSpace())
+            ++str;
+    }
+}
+static void pathArcSegment(QPainterPath &path,
+                           qreal xc, qreal yc,
+                           qreal th0, qreal th1,
+                           qreal rx, qreal ry, qreal xAxisRotation)
+{
+    qreal sinTh, cosTh;
+    qreal a00, a01, a10, a11;
+    qreal x1, y1, x2, y2, x3, y3;
+    qreal t;
+    qreal thHalf;
+
+    sinTh = qSin(xAxisRotation * (3.141592653589793 / 180.0));
+    cosTh = qCos(xAxisRotation * (3.141592653589793 / 180.0));
+
+    a00 =  cosTh * rx;
+    a01 = -sinTh * ry;
+    a10 =  sinTh * rx;
+    a11 =  cosTh * ry;
+
+    thHalf = 0.5 * (th1 - th0);
+    t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf);
+    x1 = xc + qCos(th0) - t * qSin(th0);
+    y1 = yc + qSin(th0) + t * qCos(th0);
+    x3 = xc + qCos(th1);
+    y3 = yc + qSin(th1);
+    x2 = x3 + t * qSin(th1);
+    y2 = y3 - t * qCos(th1);
+
+    path.cubicTo(a00 * x1 + a01 * y1, a10 * x1 + a11 * y1,
+                 a00 * x2 + a01 * y2, a10 * x2 + a11 * y2,
+                 a00 * x3 + a01 * y3, a10 * x3 + a11 * y3);
+}
+
+// the arc handling code underneath is from XSVG (BSD license)
+/*
+ * Copyright  2002 USC/Information Sciences Institute
+ *
+ * Permission to use, copy, modify, distribute, and sell this software
+ * and its documentation for any purpose is hereby granted without
+ * fee, provided that the above copyright notice appear in all copies
+ * and that both that copyright notice and this permission notice
+ * appear in supporting documentation, and that the name of
+ * Information Sciences Institute not be used in advertising or
+ * publicity pertaining to distribution of the software without
+ * specific, written prior permission.  Information Sciences Institute
+ * makes no representations about the suitability of this software for
+ * any purpose.  It is provided "as is" without express or implied
+ * warranty.
+ *
+ * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD
+ * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES
+ * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
+ * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
+ * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
+ * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+static void pathArc(QPainterPath &path,
+                    qreal               rx,
+                    qreal               ry,
+                    qreal               x_axis_rotation,
+                    int         large_arc_flag,
+                    int         sweep_flag,
+                    qreal               x,
+                    qreal               y,
+                    qreal curx, qreal cury)
+{
+    qreal sin_th, cos_th;
+    qreal a00, a01, a10, a11;
+    qreal x0, y0, x1, y1, xc, yc;
+    qreal d, sfactor, sfactor_sq;
+    qreal th0, th1, th_arc;
+    int i, n_segs;
+    qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check;
+
+    rx = qAbs(rx);
+    ry = qAbs(ry);
+
+    sin_th = qSin(x_axis_rotation * (3.141592653589793 / 180.0));
+    cos_th = qCos(x_axis_rotation * (3.141592653589793 / 180.0));
+
+    dx = (curx - x) / 2.0;
+    dy = (cury - y) / 2.0;
+    dx1 =  cos_th * dx + sin_th * dy;
+    dy1 = -sin_th * dx + cos_th * dy;
+    Pr1 = rx * rx;
+    Pr2 = ry * ry;
+    Px = dx1 * dx1;
+    Py = dy1 * dy1;
+    /* Spec : check if radii are large enough */
+    check = Px / Pr1 + Py / Pr2;
+    if (check > 1) {
+        rx = rx * qSqrt(check);
+        ry = ry * qSqrt(check);
+    }
+
+    a00 =  cos_th / rx;
+    a01 =  sin_th / rx;
+    a10 = -sin_th / ry;
+    a11 =  cos_th / ry;
+    x0 = a00 * curx + a01 * cury;
+    y0 = a10 * curx + a11 * cury;
+    x1 = a00 * x + a01 * y;
+    y1 = a10 * x + a11 * y;
+    /* (x0, y0) is current point in transformed coordinate space.
+       (x1, y1) is new point in transformed coordinate space.
+
+       The arc fits a unit-radius circle in this space.
+    */
+    d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0);
+    sfactor_sq = 1.0 / d - 0.25;
+    if (sfactor_sq < 0) sfactor_sq = 0;
+    sfactor = qSqrt(sfactor_sq);
+    if (sweep_flag == large_arc_flag) sfactor = -sfactor;
+    xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0);
+    yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0);
+    /* (xc, yc) is center of the circle. */
+
+    th0 = qAtan2(y0 - yc, x0 - xc);
+    th1 = qAtan2(y1 - yc, x1 - xc);
+
+    th_arc = th1 - th0;
+    if (th_arc < 0 && sweep_flag)
+        th_arc += 2 * 3.141592653589793;
+    else if (th_arc > 0 && !sweep_flag)
+        th_arc -= 2 * 3.141592653589793;
+
+    n_segs = qCeil(qAbs(th_arc / (3.141592653589793 * 0.5 + 0.001)));
+
+    for (i = 0; i < n_segs; i++) {
+        pathArcSegment(path, xc, yc,
+                       th0 + i * th_arc / n_segs,
+                       th0 + (i + 1) * th_arc / n_segs,
+                       rx, ry, x_axis_rotation);
+    }
+}
+
+static QTransform parseTransformationMatrix(const QStringRef &value)
+{
+    if (value.isEmpty())
+        return QTransform();
+
+    QTransform matrix;
+    const QChar *str = value.constData();
+    const QChar *end = str + value.length();
+
+    while (str < end) {
+        if (str->isSpace() || *str == QLatin1Char(',')) {
+            ++str;
+            continue;
+        }
+        enum State {
+            Matrix,
+            Translate,
+            Rotate,
+            Scale,
+            SkewX,
+            SkewY
+        };
+        State state = Matrix;
+        if (*str == QLatin1Char('m')) {  //matrix
+            const char *ident = "atrix";
+            for (int i = 0; i < 5; ++i)
+                if (*(++str) != QLatin1Char(ident[i]))
+                    goto error;
+            ++str;
+            state = Matrix;
+        } else if (*str == QLatin1Char('t')) { //translate
+            const char *ident = "ranslate";
+            for (int i = 0; i < 8; ++i)
+                if (*(++str) != QLatin1Char(ident[i]))
+                    goto error;
+            ++str;
+            state = Translate;
+        } else if (*str == QLatin1Char('r')) { //rotate
+            const char *ident = "otate";
+            for (int i = 0; i < 5; ++i)
+                if (*(++str) != QLatin1Char(ident[i]))
+                    goto error;
+            ++str;
+            state = Rotate;
+        } else if (*str == QLatin1Char('s')) { //scale, skewX, skewY
+            ++str;
+            if (*str == QLatin1Char('c')) {
+                const char *ident = "ale";
+                for (int i = 0; i < 3; ++i)
+                    if (*(++str) != QLatin1Char(ident[i]))
+                        goto error;
+                ++str;
+                state = Scale;
+            } else if (*str == QLatin1Char('k')) {
+                if (*(++str) != QLatin1Char('e'))
+                    goto error;
+                if (*(++str) != QLatin1Char('w'))
+                    goto error;
+                ++str;
+                if (*str == QLatin1Char('X'))
+                    state = SkewX;
+                else if (*str == QLatin1Char('Y'))
+                    state = SkewY;
+                else
+                    goto error;
+                ++str;
+            } else {
+                goto error;
+            }
+        } else {
+            goto error;
+        }
+
+
+        while (str < end && str->isSpace())
+            ++str;
+        if (*str != QLatin1Char('('))
+            goto error;
+        ++str;
+        QVarLengthArray<qreal, 8> points;
+        parseNumbersArray(str, points);
+        if (*str != QLatin1Char(')'))
+            goto error;
+        ++str;
+
+        if(state == Matrix) {
+            if(points.count() != 6)
+                goto error;
+            matrix = QTransform(points[0], points[1],
+                                points[2], points[3],
+                                points[4], points[5]) * matrix;
+        } else if (state == Translate) {
+            if (points.count() == 1)
+                matrix.translate(points[0], 0);
+            else if (points.count() == 2)
+                matrix.translate(points[0], points[1]);
+            else
+                goto error;
+        } else if (state == Rotate) {
+            if(points.count() == 1) {
+                matrix.rotate(points[0]);
+            } else if (points.count() == 3) {
+                matrix.translate(points[1], points[2]);
+                matrix.rotate(points[0]);
+                matrix.translate(-points[1], -points[2]);
+            } else {
+                goto error;
+            }
+        } else if (state == Scale) {
+            if (points.count() < 1 || points.count() > 2)
+                goto error;
+            qreal sx = points[0];
+            qreal sy = sx;
+            if(points.count() == 2)
+                sy = points[1];
+            matrix.scale(sx, sy);
+        } else if (state == SkewX) {
+            if (points.count() != 1)
+                goto error;
+            const qreal deg2rad = qreal(0.017453292519943295769);
+            matrix.shear(qTan(points[0]*deg2rad), 0);
+        } else if (state == SkewY) {
+            if (points.count() != 1)
+                goto error;
+            const qreal deg2rad = qreal(0.017453292519943295769);
+            matrix.shear(0, qTan(points[0]*deg2rad));
+        }
+    }
+  error:
+    return matrix;
+}
+
+static bool parsePathDataFast(const QStringRef &dataStr, QPainterPath &path)
+{
+    qreal x0 = 0, y0 = 0;              // starting point
+    qreal x = 0, y = 0;                // current point
+    char lastMode = 0;
+    QPointF ctrlPt;
+    const QChar *str = dataStr.constData();
+    const QChar *end = str + dataStr.size();
+
+    while (str != end) {
+        while (str->isSpace() && (str + 1) != end)
+            ++str;
+        QChar pathElem = *str;
+        ++str;
+        QChar endc = *end;
+        *const_cast<QChar *>(end) = 0; // parseNumbersArray requires 0-termination that QStringRef cannot guarantee
+        QVarLengthArray<qreal, 8> arg;
+        parseNumbersArray(str, arg);
+        *const_cast<QChar *>(end) = endc;
+        if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z'))
+            arg.append(0);//dummy
+        const qreal *num = arg.constData();
+        int count = arg.count();
+        while (count > 0) {
+            qreal offsetX = x;        // correction offsets
+            qreal offsetY = y;        // for relative commands
+            switch (pathElem.unicode()) {
+            case 'm': {
+                if (count < 2) {
+                    num++;
+                    count--;
+                    break;
+                }
+                x = x0 = num[0] + offsetX;
+                y = y0 = num[1] + offsetY;
+                num += 2;
+                count -= 2;
+                path.moveTo(x0, y0);
+
+                 // As per 1.2  spec 8.3.2 The "moveto" commands
+                 // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
+                 // the subsequent pairs shall be treated as implicit 'lineto' commands.
+                 pathElem = QLatin1Char('l');
+            }
+                break;
+            case 'M': {
+                if (count < 2) {
+                    num++;
+                    count--;
+                    break;
+                }
+                x = x0 = num[0];
+                y = y0 = num[1];
+                num += 2;
+                count -= 2;
+                path.moveTo(x0, y0);
+
+                // As per 1.2  spec 8.3.2 The "moveto" commands
+                // If a 'moveto' is followed by multiple pairs of coordinates without explicit commands,
+                // the subsequent pairs shall be treated as implicit 'lineto' commands.
+                pathElem = QLatin1Char('L');
+            }
+                break;
+            case 'z':
+            case 'Z': {
+                x = x0;
+                y = y0;
+                count--; // skip dummy
+                num++;
+                path.closeSubpath();
+            }
+                break;
+            case 'l': {
+                if (count < 2) {
+                    num++;
+                    count--;
+                    break;
+                }
+                x = num[0] + offsetX;
+                y = num[1] + offsetY;
+                num += 2;
+                count -= 2;
+                path.lineTo(x, y);
+
+            }
+                break;
+            case 'L': {
+                if (count < 2) {
+                    num++;
+                    count--;
+                    break;
+                }
+                x = num[0];
+                y = num[1];
+                num += 2;
+                count -= 2;
+                path.lineTo(x, y);
+            }
+                break;
+            case 'h': {
+                x = num[0] + offsetX;
+                num++;
+                count--;
+                path.lineTo(x, y);
+            }
+                break;
+            case 'H': {
+                x = num[0];
+                num++;
+                count--;
+                path.lineTo(x, y);
+            }
+                break;
+            case 'v': {
+                y = num[0] + offsetY;
+                num++;
+                count--;
+                path.lineTo(x, y);
+            }
+                break;
+            case 'V': {
+                y = num[0];
+                num++;
+                count--;
+                path.lineTo(x, y);
+            }
+                break;
+            case 'c': {
+                if (count < 6) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                QPointF c1(num[0] + offsetX, num[1] + offsetY);
+                QPointF c2(num[2] + offsetX, num[3] + offsetY);
+                QPointF e(num[4] + offsetX, num[5] + offsetY);
+                num += 6;
+                count -= 6;
+                path.cubicTo(c1, c2, e);
+                ctrlPt = c2;
+                x = e.x();
+                y = e.y();
+                break;
+            }
+            case 'C': {
+                if (count < 6) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                QPointF c1(num[0], num[1]);
+                QPointF c2(num[2], num[3]);
+                QPointF e(num[4], num[5]);
+                num += 6;
+                count -= 6;
+                path.cubicTo(c1, c2, e);
+                ctrlPt = c2;
+                x = e.x();
+                y = e.y();
+                break;
+            }
+            case 's': {
+                if (count < 4) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                QPointF c1;
+                if (lastMode == 'c' || lastMode == 'C' ||
+                    lastMode == 's' || lastMode == 'S')
+                    c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
+                else
+                    c1 = QPointF(x, y);
+                QPointF c2(num[0] + offsetX, num[1] + offsetY);
+                QPointF e(num[2] + offsetX, num[3] + offsetY);
+                num += 4;
+                count -= 4;
+                path.cubicTo(c1, c2, e);
+                ctrlPt = c2;
+                x = e.x();
+                y = e.y();
+                break;
+            }
+            case 'S': {
+                if (count < 4) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                QPointF c1;
+                if (lastMode == 'c' || lastMode == 'C' ||
+                    lastMode == 's' || lastMode == 'S')
+                    c1 = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
+                else
+                    c1 = QPointF(x, y);
+                QPointF c2(num[0], num[1]);
+                QPointF e(num[2], num[3]);
+                num += 4;
+                count -= 4;
+                path.cubicTo(c1, c2, e);
+                ctrlPt = c2;
+                x = e.x();
+                y = e.y();
+                break;
+            }
+            case 'q': {
+                if (count < 4) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                QPointF c(num[0] + offsetX, num[1] + offsetY);
+                QPointF e(num[2] + offsetX, num[3] + offsetY);
+                num += 4;
+                count -= 4;
+                path.quadTo(c, e);
+                ctrlPt = c;
+                x = e.x();
+                y = e.y();
+                break;
+            }
+            case 'Q': {
+                if (count < 4) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                QPointF c(num[0], num[1]);
+                QPointF e(num[2], num[3]);
+                num += 4;
+                count -= 4;
+                path.quadTo(c, e);
+                ctrlPt = c;
+                x = e.x();
+                y = e.y();
+                break;
+            }
+            case 't': {
+                if (count < 2) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                QPointF e(num[0] + offsetX, num[1] + offsetY);
+                num += 2;
+                count -= 2;
+                QPointF c;
+                if (lastMode == 'q' || lastMode == 'Q' ||
+                    lastMode == 't' || lastMode == 'T')
+                    c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
+                else
+                    c = QPointF(x, y);
+                path.quadTo(c, e);
+                ctrlPt = c;
+                x = e.x();
+                y = e.y();
+                break;
+            }
+            case 'T': {
+                if (count < 2) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                QPointF e(num[0], num[1]);
+                num += 2;
+                count -= 2;
+                QPointF c;
+                if (lastMode == 'q' || lastMode == 'Q' ||
+                    lastMode == 't' || lastMode == 'T')
+                    c = QPointF(2*x-ctrlPt.x(), 2*y-ctrlPt.y());
+                else
+                    c = QPointF(x, y);
+                path.quadTo(c, e);
+                ctrlPt = c;
+                x = e.x();
+                y = e.y();
+                break;
+            }
+            case 'a': {
+                if (count < 7) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                qreal rx = (*num++);
+                qreal ry = (*num++);
+                qreal xAxisRotation = (*num++);
+                qreal largeArcFlag  = (*num++);
+                qreal sweepFlag = (*num++);
+                qreal ex = (*num++) + offsetX;
+                qreal ey = (*num++) + offsetY;
+                count -= 7;
+                qreal curx = x;
+                qreal cury = y;
+                pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
+                        int(sweepFlag), ex, ey, curx, cury);
+
+                x = ex;
+                y = ey;
+            }
+                break;
+            case 'A': {
+                if (count < 7) {
+                    num += count;
+                    count = 0;
+                    break;
+                }
+                qreal rx = (*num++);
+                qreal ry = (*num++);
+                qreal xAxisRotation = (*num++);
+                qreal largeArcFlag  = (*num++);
+                qreal sweepFlag = (*num++);
+                qreal ex = (*num++);
+                qreal ey = (*num++);
+                count -= 7;
+                qreal curx = x;
+                qreal cury = y;
+                pathArc(path, rx, ry, xAxisRotation, int(largeArcFlag),
+                        int(sweepFlag), ex, ey, curx, cury);
+
+                x = ex;
+                y = ey;
+            }
+                break;
+            default:
+                return false;
+            }
+            lastMode = pathElem.toLatin1();
+        }
+    }
+    return true;
+}
--- a/src/octave-svgconvert.cc	Fri Apr 09 09:38:40 2021 -0400
+++ b/src/octave-svgconvert.cc	Wed Apr 07 22:21:36 2021 +0200
@@ -38,46 +38,66 @@
 #include <QPainter>
 #include <QPrinter>
 #include <QRegExp>
-#include <QSvgRenderer>
+
+// Include a set of path rendering functions extracted from Qt-5.12 source
+#include "octave-qsvghandler.h"
 
 // Render to pdf
 class pdfpainter : public QPainter
 {
 public:
-  pdfpainter (QString fname, QRectF sz, double dpi)
-    : m_printer ()
+  pdfpainter (QString fname, QRectF sz)
+      :  m_printer ()
   {
-    double scl = dpi / 72.0;
-    sz.setWidth (sz.width () * scl);
-    sz.setHeight (sz.height () * scl);
-
     // Printer settings
     m_printer.setOutputFormat (QPrinter::PdfFormat);
     m_printer.setFontEmbeddingEnabled (true);
     m_printer.setOutputFileName (fname);
     m_printer.setFullPage (true);
 #if defined (HAVE_QPRINTER_SETPAGESIZE)
-    m_printer.setPageSize (QPageSize (sz.size (), QPageSize::Point));
+    m_printer.setPageSize (QPageSize (sz.size (), QPageSize::Point,
+                                      QString ("custom"),
+                                      QPageSize::ExactMatch));
 #else
-    m_printer.setPaperSize (sz.size (), QPrinter::DevicePixel);
+    m_printer.setPaperSize (sz.size (), QPrinter::Point);
 #endif
+
+    // Painter settings
+    begin (&m_printer);
+    setWindow (sz.toRect ());
   }
 
-  ~pdfpainter (void) { }
-
-  void render (const QByteArray svg_content)
-  {
-    QSvgRenderer renderer (svg_content);
-    begin (&m_printer);
-    renderer.render (this);
-    end ();
-  }
+  ~pdfpainter (void) { end (); }
 
 private:
   QPrinter m_printer;
 };
 
-// String conversion functions
+// String conversion functions+QVector<double> qstr2vectorf (QString str)
+QVector<double> qstr2vectorf (QString str)
+{
+  QVector<double> pts;
+  QStringList coords = str.split (",");
+  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 1)
+    {
+      double pt = (*p).toDouble ();
+      pts.append (pt);
+    }
+  return pts;
+}
+
+QVector<double> qstr2vectord (QString str)
+{
+  QVector<double> pts;
+  QStringList coords = str.split (",");
+  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 1)
+    {
+      double pt = (*p).toDouble ();
+      pts.append (pt);
+    }
+
+  return pts;
+}
 
 QVector<QPointF> qstr2ptsvector (QString str)
 {
@@ -93,6 +113,33 @@
   return pts;
 }
 
+QVector<QPoint> qstr2ptsvectord (QString str)
+{
+  QVector<QPoint> pts;
+  str = str.trimmed ();
+  str.replace (" ", ",");
+  QStringList coords = str.split (",");
+  for (QStringList::iterator p = coords.begin (); p != coords.end (); p += 2)
+    {
+      QPoint pt ((*p).toDouble (), (*(p+1)).toDouble ());
+      pts.append (pt);
+    }
+  return pts;
+}
+
+// Extract field arguments in a style-like string, e.g. "bla field(1,34,56) bla"
+QString get_field (QString str, QString field)
+{
+  QString retval;
+  QRegExp rx (field + "\\(([^\\)]*)\\)");
+  int pos = 0;
+  pos = rx.indexIn (str, pos);
+  if (pos > -1)
+    retval = rx.cap (1);
+
+  return retval;
+}
+
 // Polygon reconstruction class
 class octave_polygon
 {
@@ -281,6 +328,384 @@
   QList<QPolygonF> m_polygons;
 };
 
+void draw (QDomElement& parent_elt, pdfpainter& painter)
+{
+  QDomNodeList nodes = parent_elt.childNodes ();
+
+  static QString clippath_id;
+  static QMap< QString, QVector<QPoint> > clippath;
+
+  // tspan elements must have access to the font and position extracted from
+  // their parent text element
+  static QFont font;
+  static double dx = 0, dy = 0;
+
+  // Store path defined in <defs> in a map
+  static bool in_defs = false;
+  static QMap< QString, QPainterPath> path_map;
+
+  for (int i = 0; i < nodes.count (); i++)
+    {
+      QDomNode node = nodes.at (i);
+      if (! node.isElement ())
+        continue;
+
+      QDomElement elt = node.toElement ();
+
+      if (elt.tagName () == "clipPath")
+        {
+          clippath_id = "#" + elt.attribute ("id");
+          draw (elt, painter);
+          clippath_id = QString ();
+        }
+      else if (elt.tagName () == "g")
+        {
+          QString str = elt.attribute ("font-family");
+          if (! str.isEmpty ())
+            {
+              // Font
+              font = QFont ();
+              font.setFamily (elt.attribute ("font-family"));
+
+              str = elt.attribute ("font-weight");
+              if (! str.isEmpty () && str != "normal")
+                font.setWeight (QFont::Bold);
+
+              str = elt.attribute ("font-style");
+              if (! str.isEmpty () && str != "normal")
+                font.setStyle (QFont::StyleItalic);
+
+              str = elt.attribute ("font-size");
+              if (! str.isEmpty ())
+                font.setPixelSize (str.toDouble ());
+
+              painter.setFont (font);
+
+              // Translation and rotation
+              painter.save ();
+              str = get_field (elt.attribute ("transform"), "translate");
+              if (! str.isEmpty ())
+                {
+                  QStringList trans = str.split (",");
+                  dx = trans[0].toDouble ();
+                  dy = trans[1].toDouble ();
+
+                  str = get_field (elt.attribute ("transform"), "rotate");
+                  if (! str.isEmpty ())
+                    {
+                      QStringList rot = str.split (",");
+                      painter.translate (dx+rot[1].toDouble (),
+                                         dy+rot[2].toDouble ());
+                      painter.rotate (rot[0].toDouble ());
+                      dx = rot[1].toDouble ();
+                      dy = rot[2].toDouble ();
+                    }
+                  else
+                    {
+                      painter.translate (dx, dy);
+                      dx = 0;
+                      dy = 0;
+                    }
+                }
+
+              draw (elt, painter);
+              painter.restore ();
+            }
+          else
+            {
+              bool current_clipstate = painter.hasClipping ();
+              QRegion current_clippath = painter.clipRegion ();
+
+              str = elt.attribute ("clip-path");
+              if (! str.isEmpty ())
+                {
+                  QVector<QPoint> pts = clippath[get_field (str, "url")];
+                  if (! pts.isEmpty ())
+                    {
+                      painter.setClipRegion (QRegion (QPolygon (pts)));
+                      painter.setClipping (true);
+                    }
+                }
+
+              // Fill color
+              str = get_field (elt.attribute ("fill"), "rgb");
+              if (! str.isEmpty ())
+                {
+                  QStringList clist = str.split (",");
+                  painter.setBrush (QColor (clist[0].toInt (),
+                                            clist[1].toInt (),
+                                            clist[2].toInt ()));
+                }
+
+              // Transform
+              str = elt.attribute ("transform");
+              painter.save ();
+              if (! str.isEmpty ())
+                {
+                  QStringRef tf (&str);
+                  QTransform  tform =
+                    parseTransformationMatrix (tf) * painter.transform ();
+                  painter.setTransform (tform);
+                }
+
+              draw (elt, painter);
+
+              // Restore previous clipping settings
+              painter.restore  ();
+              painter.setClipRegion (current_clippath);
+              painter.setClipping (current_clipstate);
+            }
+        }
+      else if (elt.tagName () == "defs")
+        {
+          in_defs = true;
+          draw (elt, painter);
+          in_defs = false;
+        }
+      else if (elt.tagName () == "path")
+        {
+          // Store QPainterPath for latter use
+          QString id = elt.attribute ("id");
+          if (! id.isEmpty ())
+            {
+              QString d = elt.attribute ("d");
+
+              if (! d.isEmpty ())
+                {
+                  QStringRef data (&d);
+                  QPainterPath path;
+                  if (! parsePathDataFast (data, path))
+                    continue; // Something went wrong, pass
+                  else if (path.isEmpty ())
+                    std::cout << "Empty path for data:"
+                              << d.toStdString () << std::endl;
+                  else if (in_defs)
+                    path_map["#" + id] = path;
+                  else
+                    painter.drawPath (path);
+
+
+                  if (path_map["#" + id].isEmpty ())
+                    std::cout << "Empty path for data:"
+                              << d.toStdString () << std::endl;
+                }
+            }
+        }
+      else if (elt.tagName () == "use")
+        {
+          painter.setPen (Qt::NoPen);
+
+          QString str = elt.attribute ("xlink:href");
+          if (! str.isEmpty () && str.size () > 2)
+            {
+              QPainterPath path = path_map[str];
+              if (! path.isEmpty ())
+                {
+                  str = elt.attribute ("x");
+                  double x = elt.attribute ("x").toDouble ();
+                  str = elt.attribute ("y");
+                  double y = elt.attribute ("y").toDouble ();
+                  painter.translate (x, y);
+                  painter.drawPath (path);
+                  painter.translate (-x, -y);
+                }
+            }
+        }
+      else if (elt.tagName () == "text")
+        {
+          // Font
+          QFont saved_font (font);
+
+          QString str = elt.attribute ("font-family");
+          if (! str.isEmpty ())
+            font.setFamily (elt.attribute ("font-family"));
+
+          str = elt.attribute ("font-weight");
+          if (! str.isEmpty ())
+            {
+              if (str != "normal")
+                font.setWeight (QFont::Bold);
+              else
+                font.setWeight (QFont::Normal);
+            }
+
+          str = elt.attribute ("font-style");
+          if (! str.isEmpty ())
+            {
+              if (str != "normal")
+                font.setStyle (QFont::StyleItalic);
+              else
+                font.setStyle (QFont::StyleNormal);
+            }
+
+          str = elt.attribute ("font-size");
+          if (! str.isEmpty ())
+            font.setPixelSize (str.toDouble ());
+
+          painter.setFont (font);
+
+          // Color is specified in rgb
+          str = get_field (elt.attribute ("fill"), "rgb");
+          if (! str.isEmpty ())
+            {
+              QStringList clist = str.split (",");
+              painter.setPen (QColor (clist[0].toInt (), clist[1].toInt (),
+                                      clist[2].toInt ()));
+            }
+
+          QStringList xx = elt.attribute ("x").split (" ");
+          int y = elt.attribute ("y").toInt ();
+          str = elt.text ();
+          if (! str.isEmpty ())
+            {
+              int ii = 0;
+              foreach (QString s,  xx)
+                if (ii < str.size ())
+                  painter.drawText (s.toInt ()-dx, y-dy, str.at (ii++));
+            }
+
+          draw (elt, painter);
+          font = saved_font;
+        }
+      else if (elt.tagName () == "polyline")
+        {
+          // Color
+          QColor c (elt.attribute ("stroke"));
+          QString str = elt.attribute ("stroke-opacity");
+          if (! str.isEmpty () && str.toDouble () != 1.0
+              && str.toDouble () >= 0.0)
+            c.setAlphaF (str.toDouble ());
+
+          QPen pen;
+          pen.setColor (c);
+
+          // Line properties
+          str = elt.attribute ("stroke-width");
+          if (! str.isEmpty ())
+            {
+              double w = str.toDouble ();
+              if (w > 0)
+                pen.setWidthF (w);
+            }
+
+          str = elt.attribute ("stroke-linecap");
+          pen.setCapStyle (Qt::SquareCap);
+          if (str == "round")
+            pen.setCapStyle (Qt::RoundCap);
+          else if (str == "butt")
+            pen.setCapStyle (Qt::FlatCap);
+
+          str = elt.attribute ("stroke-linejoin");
+          pen.setJoinStyle (Qt::MiterJoin);
+          if (str == "round")
+            pen.setJoinStyle (Qt::RoundJoin);
+          else if (str == "bevel")
+            pen.setJoinStyle (Qt::BevelJoin);
+
+          str = elt.attribute ("stroke-dasharray");
+          pen.setStyle (Qt::SolidLine);
+          if (! str.isEmpty ())
+            {
+              QVector<double> pat = qstr2vectord (str);
+              if (pat.count () != 2 || pat[1] != 0)
+                {
+                  // Express pattern in linewidth units
+                  for (auto& p : pat)
+                    p /= pen.widthF ();
+
+                  pen.setDashPattern (pat);
+                }
+            }
+
+          painter.setPen (pen);
+          painter.drawPolyline (qstr2ptsvector (elt.attribute ("points")));
+        }
+      else if (elt.tagName () == "image")
+        {
+          // Images are represented as a base64 stream of png formatted data
+          QString href_att = elt.attribute ("xlink:href");
+          QString prefix ("data:image/png;base64,");
+          QByteArray data
+            = QByteArray::fromBase64 (href_att.mid (prefix.length ()).toLatin1 ());
+          QImage img;
+          if (img.loadFromData (data, "PNG"))
+            {
+              QRect pos(elt.attribute ("x").toInt (),
+                        elt.attribute ("y").toInt (),
+                        elt.attribute ("width").toInt (),
+                        elt.attribute ("height").toInt ());
+
+              // Translate
+              painter.save ();
+              QString str = get_field (elt.attribute ("transform"), "matrix");
+              if (! str.isEmpty ())
+                {
+                  QVector<double> m = qstr2vectorf (str);
+                  QTransform tform(m[0], m[1], m[2],
+                                   m[3], m[4], m[5]);
+                  painter.setTransform (tform);
+                }
+
+              painter.setRenderHint (QPainter::Antialiasing, false);
+              painter.drawImage (pos, img);
+              painter.setRenderHint (QPainter::Antialiasing, true);
+              painter.restore  ();
+            }
+        }
+      else if (elt.tagName () == "rect")
+        {
+          // Color
+          QColor col (Qt::black);
+          QString str = elt.attribute ("fill");
+          if (! str.isEmpty ())
+            col = QColor (str);
+
+          // Position
+          double x = elt.attribute ("x").toDouble ();
+          double y = elt.attribute ("y").toDouble ();
+
+          // Size
+          double wd = elt.attribute ("width").toDouble ();
+          double hg = elt.attribute ("height").toDouble ();
+
+          painter.setBrush (col);
+          painter.setPen (Qt::NoPen);
+
+          painter.drawRect (QRectF (x, y, wd, hg));
+        }
+      else if (elt.tagName () == "polygon")
+        {
+          if (! clippath_id.isEmpty ())
+            clippath[clippath_id] = qstr2ptsvectord (elt.attribute ("points"));
+          else
+            {
+              QString str = elt.attribute ("fill");
+              if (! str.isEmpty ())
+                {
+                  QColor color (str);
+
+                  str = elt.attribute ("fill-opacity");
+                  if (! str.isEmpty () && str.toDouble () != 1.0
+                      && str.toDouble () >= 0.0)
+                    color.setAlphaF (str.toDouble ());
+
+                  QPolygonF p (qstr2ptsvector (elt.attribute ("points")));
+
+                  if (p.count () > 2)
+                    {
+                      painter.setBrush (color);
+                      painter.setPen (Qt::NoPen);
+
+                      painter.setRenderHint (QPainter::Antialiasing, false);
+                      painter.drawPolygon (p);
+                      painter.setRenderHint (QPainter::Antialiasing, true);
+                    }
+                }
+            }
+        }
+    }
+}
+
 // Append a list of reconstructed child polygons to a QDomElement and remove
 // the original nodes
 
@@ -390,61 +815,6 @@
     replace_polygons (parent_elt, collection[ii].first, collection[ii].second);
 }
 
-// Split "text" elements into single characters elements
-void split_strings (QDomElement& parent_elt)
-{
-  QDomNodeList nodes = parent_elt.childNodes ();
-
-  for (int ii = 0; ii < nodes.count (); ii++)
-    {
-      QDomNode node = nodes.at (ii);
-
-      if (! node.isElement ())
-        continue;
-
-      QDomElement elt = node.toElement ();
-
-      if (elt.tagName () == "text")
-        {
-          QString str = elt.attribute ("x");
-          if (! str.isEmpty ())
-            {
-              QStringList xx = elt.attribute ("x").split (" ");
-              str = elt.text ();
-
-              if (! str.isEmpty () && xx.count () > 1)
-                {
-                  QDomNode last_node = node;
-
-                  for (int jj = 0; jj < (xx.count ()) && (jj < str.size ());
-                       jj++)
-                    {
-                      if (! last_node.isNull ())
-                        {
-                          QDomNode new_node = node.cloneNode ();
-                          new_node.toElement ().setAttribute ("x", xx.at (jj));
-
-                          QDomNodeList subnodes = new_node.childNodes ();
-
-                          // Change the text node of this element
-                          for (int kk = 0; kk < subnodes.count (); kk++)
-                            if (subnodes.at (kk).isText ())
-                              subnodes.at (kk).toText ().setData (str.at (jj));
-
-                          parent_elt.insertAfter (new_node, last_node);
-                          last_node = new_node;
-                        }
-                    }
-
-                  parent_elt.removeChild (node);
-                }
-            }
-        }
-      else
-        split_strings (elt);
-    }
-}
-
 int main(int argc, char *argv[])
 {
   const char *doc = "See \"octave-svgconvert -h\"";
@@ -488,7 +858,7 @@
       // Read from stdin
       if (! file.open (stdin, QIODevice::ReadOnly | QIODevice::Text))
         {
-          std::cerr << "Unable read from stdin\n";
+          std::cerr << "Unable to read from stdin\n";
           std::cerr << doc;
           return -1;
         }
@@ -515,14 +885,9 @@
       return -1;
     }
 
-  // Resolution
-  double dpi = QString (argv[3]).toDouble ();
-  if (dpi <= 0.0)
-    {
-      std::cerr << "DPI must be positive\n";
-      return -1;
-    }
-
+  // Resolution (Currently unused). Keep the DPI argument in case
+  // we implement raster outputs.
+  // double dpi = QString (argv[3]).toDouble ();
 
   // Get the viewport from the root element
   QDomElement root = document.firstChildElement();
@@ -568,18 +933,10 @@
   // Draw
   if (! strcmp (argv[2], "pdf"))
     {
-      // Remove clippath which is not supported in svg-tiny
-      QDomNodeList lst = root.elementsByTagName ("clipPath");
-      for (int ii = lst.count (); ii > 0; ii--)
-        lst.at (ii-1).parentNode ().removeChild (lst.at (ii-1));
+      // PDF painter
+      pdfpainter painter (fout.fileName (), vp);
 
-      // Split text strings into single characters with single x coordinates
-      split_strings (root);
-
-      // PDF painter
-      pdfpainter painter (fout.fileName (), vp, dpi);
-
-      painter.render (document.toByteArray ());
+      draw (root, painter);
     }
   else
     {