var SVGtoPDF = function(doc, svg, x, y, options) {
    "use strict";

    const NamedColors = {aliceblue: [240,248,255], antiquewhite: [250,235,215], aqua: [0,255,255], aquamarine: [127,255,212], azure: [240,255,255], beige: [245,245,220], bisque: [255,228,196], black: [0,0,0], blanchedalmond: [255,235,205], blue: [0,0,255], blueviolet: [138,43,226], brown: [165,42,42], burlywood: [222,184,135], cadetblue: [95,158,160], chartreuse: [127,255,0],
      chocolate: [210,105,30], coral: [255,127,80], cornflowerblue: [100,149,237], cornsilk: [255,248,220], crimson: [220,20,60], cyan: [0,255,255], darkblue: [0,0,139], darkcyan: [0,139,139], darkgoldenrod: [184,134,11], darkgray: [169,169,169], darkgrey: [169,169,169], darkgreen: [0,100,0], darkkhaki: [189,183,107], darkmagenta: [139,0,139], darkolivegreen: [85,107,47],
      darkorange: [255,140,0], darkorchid: [153,50,204], darkred: [139,0,0], darksalmon: [233,150,122], darkseagreen: [143,188,143], darkslateblue: [72,61,139], darkslategray: [47,79,79], darkslategrey: [47,79,79], darkturquoise: [0,206,209], darkviolet: [148,0,211], deeppink: [255,20,147], deepskyblue: [0,191,255], dimgray: [105,105,105], dimgrey: [105,105,105],
      dodgerblue: [30,144,255], firebrick: [178,34,34], floralwhite: [255,250,240], forestgreen: [34,139,34], fuchsia: [255,0,255], gainsboro: [220,220,220], ghostwhite: [248,248,255], gold: [255,215,0], goldenrod: [218,165,32], gray: [128,128,128], grey: [128,128,128], green: [0,128,0], greenyellow: [173,255,47], honeydew: [240,255,240], hotpink: [255,105,180],
      indianred: [205,92,92], indigo: [75,0,130], ivory: [255,255,240], khaki: [240,230,140], lavender: [230,230,250], lavenderblush: [255,240,245], lawngreen: [124,252,0], lemonchiffon: [255,250,205], lightblue: [173,216,230], lightcoral: [240,128,128], lightcyan: [224,255,255], lightgoldenrodyellow: [250,250,210], lightgray: [211,211,211], lightgrey: [211,211,211],
      lightgreen: [144,238,144], lightpink: [255,182,193], lightsalmon: [255,160,122], lightseagreen: [32,178,170], lightskyblue: [135,206,250], lightslategray: [119,136,153], lightslategrey: [119,136,153], lightsteelblue: [176,196,222], lightyellow: [255,255,224], lime: [0,255,0], limegreen: [50,205,50], linen: [250,240,230], magenta: [255,0,255], maroon: [128,0,0],
      mediumaquamarine: [102,205,170], mediumblue: [0,0,205], mediumorchid: [186,85,211], mediumpurple: [147,112,219], mediumseagreen: [60,179,113], mediumslateblue: [123,104,238], mediumspringgreen: [0,250,154], mediumturquoise: [72,209,204], mediumvioletred: [199,21,133], midnightblue: [25,25,112], mintcream: [245,255,250], mistyrose: [255,228,225], moccasin: [255,228,181],
      navajowhite: [255,222,173], navy: [0,0,128], oldlace: [253,245,230], olive: [128,128,0], olivedrab: [107,142,35], orange: [255,165,0], orangered: [255,69,0], orchid: [218,112,214], palegoldenrod: [238,232,170], palegreen: [152,251,152], paleturquoise: [175,238,238], palevioletred: [219,112,147], papayawhip: [255,239,213], peachpuff: [255,218,185], peru: [205,133,63],
      pink: [255,192,203], plum: [221,160,221], powderblue: [176,224,230], purple: [128,0,128], rebeccapurple: [102,51,153], red: [255,0,0], rosybrown: [188,143,143], royalblue: [65,105,225], saddlebrown: [139,69,19], salmon: [250,128,114], sandybrown: [244,164,96], seagreen: [46,139,87], seashell: [255,245,238], sienna: [160,82,45], silver: [192,192,192], skyblue: [135,206,235],
      slateblue: [106,90,205], slategray: [112,128,144], slategrey: [112,128,144], snow: [255,250,250], springgreen: [0,255,127], steelblue: [70,130,180], tan: [210,180,140], teal: [0,128,128], thistle: [216,191,216], tomato: [255,99,71], turquoise: [64,224,208], violet: [238,130,238], wheat: [245,222,179], white: [255,255,255], whitesmoke: [245,245,245], yellow: [255,255,0]};
    const DefaultColors = {black: [NamedColors.black, 1], white: [NamedColors.white, 1], transparent: [NamedColors.black, 0]};
    const Entities = {quot: 34, amp: 38, lt: 60, gt: 62, apos: 39, OElig: 338, oelig: 339, Scaron: 352, scaron: 353, Yuml: 376, circ: 710, tilde: 732, ensp: 8194, emsp: 8195, thinsp: 8201, zwnj: 8204, zwj: 8205, lrm: 8206, rlm: 8207, ndash: 8211, mdash: 8212, lsquo: 8216, rsquo: 8217, sbquo: 8218, ldquo: 8220, rdquo: 8221, bdquo: 8222, dagger: 8224, Dagger: 8225, permil: 8240, lsaquo: 8249,
      rsaquo: 8250, euro: 8364, nbsp: 160, iexcl: 161, cent: 162, pound: 163, curren: 164, yen: 165, brvbar: 166, sect: 167, uml: 168, copy: 169, ordf: 170, laquo: 171, not: 172, shy: 173, reg: 174, macr: 175, deg: 176, plusmn: 177, sup2: 178, sup3: 179, acute: 180, micro: 181, para: 182, middot: 183, cedil: 184, sup1: 185, ordm: 186, raquo: 187, frac14: 188, frac12: 189, frac34: 190,
      iquest: 191, Agrave: 192, Aacute: 193, Acirc: 194, Atilde: 195, Auml: 196, Aring: 197, AElig: 198, Ccedil: 199, Egrave: 200, Eacute: 201, Ecirc: 202, Euml: 203, Igrave: 204, Iacute: 205, Icirc: 206, Iuml: 207, ETH: 208, Ntilde: 209, Ograve: 210, Oacute: 211, Ocirc: 212, Otilde: 213, Ouml: 214, times: 215, Oslash: 216, Ugrave: 217, Uacute: 218, Ucirc: 219, Uuml: 220, Yacute: 221,
      THORN: 222, szlig: 223, agrave: 224, aacute: 225, acirc: 226, atilde: 227, auml: 228, aring: 229, aelig: 230, ccedil: 231, egrave: 232, eacute: 233, ecirc: 234, euml: 235, igrave: 236, iacute: 237, icirc: 238, iuml: 239, eth: 240, ntilde: 241, ograve: 242, oacute: 243, ocirc: 244, otilde: 245, ouml: 246, divide: 247, oslash: 248, ugrave: 249, uacute: 250, ucirc: 251, uuml: 252,
      yacute: 253, thorn: 254, yuml: 255, fnof: 402, Alpha: 913, Beta: 914, Gamma: 915, Delta: 916, Epsilon: 917, Zeta: 918, Eta: 919, Theta: 920, Iota: 921, Kappa: 922, Lambda: 923, Mu: 924, Nu: 925, Xi: 926, Omicron: 927, Pi: 928, Rho: 929, Sigma: 931, Tau: 932, Upsilon: 933, Phi: 934, Chi: 935, Psi: 936, Omega: 937, alpha: 945, beta: 946, gamma: 947, delta: 948, epsilon: 949,
      zeta: 950, eta: 951, theta: 952, iota: 953, kappa: 954, lambda: 955, mu: 956, nu: 957, xi: 958, omicron: 959, pi: 960, rho: 961, sigmaf: 962, sigma: 963, tau: 964, upsilon: 965, phi: 966, chi: 967, psi: 968, omega: 969, thetasym: 977, upsih: 978, piv: 982, bull: 8226, hellip: 8230, prime: 8242, Prime: 8243, oline: 8254, frasl: 8260, weierp: 8472, image: 8465, real: 8476,
      trade: 8482, alefsym: 8501, larr: 8592, uarr: 8593, rarr: 8594, darr: 8595, harr: 8596, crarr: 8629, lArr: 8656, uArr: 8657, rArr: 8658, dArr: 8659, hArr: 8660, forall: 8704, part: 8706, exist: 8707, empty: 8709, nabla: 8711, isin: 8712, notin: 8713, ni: 8715, prod: 8719, sum: 8721, minus: 8722, lowast: 8727, radic: 8730, prop: 8733, infin: 8734, ang: 8736, and: 8743, or: 8744,
      cap: 8745, cup: 8746, int: 8747, there4: 8756, sim: 8764, cong: 8773, asymp: 8776, ne: 8800, equiv: 8801, le: 8804, ge: 8805, sub: 8834, sup: 8835, nsub: 8836, sube: 8838, supe: 8839, oplus: 8853, otimes: 8855, perp: 8869, sdot: 8901, lceil: 8968, rceil: 8969, lfloor: 8970, rfloor: 8971, lang: 9001, rang: 9002, loz: 9674, spades: 9824, clubs: 9827, hearts: 9829, diams: 9830};
    const PathArguments = {A: 7, a: 7, C: 6, c: 6, H: 1, h: 1, L: 2, l: 2, M: 2, m: 2, Q: 4, q: 4, S: 4, s: 4, T: 2, t: 2, V: 1, v: 1, Z: 0, z: 0};
    const PathFlags = {A3: true, A4: true, a3: true, a4: true};
    const Properties = {
      'color':              {inherit: true, initial: undefined},
      'visibility':         {inherit: true, initial: 'visible', values: {'hidden': 'hidden', 'collapse': 'hidden', 'visible':'visible'}},
      'fill':               {inherit: true, initial: DefaultColors.black},
      'stroke':             {inherit: true, initial: 'none'},
      'stop-color':         {inherit: false, initial: DefaultColors.black},
      'fill-opacity':       {inherit: true, initial: 1},
      'stroke-opacity':     {inherit: true, initial: 1},
      'stop-opacity':       {inherit: false, initial: 1},
      'fill-rule':          {inherit: true, initial: 'nonzero', values: {'nonzero':'nonzero', 'evenodd':'evenodd'}},
      'clip-rule':          {inherit: true, initial: 'nonzero', values: {'nonzero':'nonzero', 'evenodd':'evenodd'}},
      'stroke-width':       {inherit: true, initial: 1},
      'stroke-dasharray':   {inherit: true, initial: []},
      'stroke-dashoffset':  {inherit: true, initial: 0},
      'stroke-miterlimit':  {inherit: true, initial: 4},
      'stroke-linejoin':    {inherit: true, initial: 'miter', values: {'miter':'miter', 'round':'round', 'bevel':'bevel'}},
      'stroke-linecap':     {inherit: true, initial: 'butt', values: {'butt':'butt', 'round':'round', 'square':'square'}},
      'font-size':          {inherit: true, initial: 16, values: {'xx-small':9, 'x-small':10, 'small':13, 'medium':16, 'large':18, 'x-large':24, 'xx-large':32}},
      'font-family':        {inherit: true, initial: 'sans-serif'},
      'font-weight':        {inherit: true, initial: 'normal', values: {'600':'bold', '700':'bold', '800':'bold', '900':'bold', 'bold':'bold', 'bolder':'bold', '500':'normal', '400':'normal', '300':'normal', '200':'normal', '100':'normal', 'normal':'normal', 'lighter':'normal'}},
      'font-style':         {inherit: true, initial: 'normal', values: {'italic':'italic', 'oblique':'italic', 'normal':'normal'}},
      'text-anchor':        {inherit: true, initial: 'start', values: {'start':'start', 'middle':'middle', 'end':'end'}},
      'direction':          {inherit: true, initial: 'ltr', values: {'ltr':'ltr', 'rtl':'rtl'}},
      'dominant-baseline':  {inherit: true, initial: 'baseline', values: {'auto':'baseline', 'baseline':'baseline', 'before-edge':'before-edge', 'text-before-edge':'before-edge', 'middle':'middle', 'central':'central', 'after-edge':'after-edge', 'text-after-edge':'after-edge', 'ideographic':'ideographic', 'alphabetic':'alphabetic', 'hanging':'hanging', 'mathematical':'mathematical'}},
      'alignment-baseline': {inherit: false, initial: undefined, values: {'auto':'baseline', 'baseline':'baseline', 'before-edge':'before-edge', 'text-before-edge':'before-edge', 'middle':'middle', 'central':'central', 'after-edge':'after-edge', 'text-after-edge':'after-edge', 'ideographic':'ideographic', 'alphabetic':'alphabetic', 'hanging':'hanging', 'mathematical':'mathematical'}},
      'baseline-shift':     {inherit: true, initial: 'baseline', values: {'baseline':'baseline', 'sub':'sub', 'super':'super'}},
      'word-spacing':       {inherit: true, initial: 0, values: {normal:0}},
      'letter-spacing':     {inherit: true, initial: 0, values: {normal:0}},
      'text-decoration':    {inherit: false, initial: 'none', values: {'none':'none', 'underline':'underline', 'overline':'overline', 'line-through':'line-through'}},
      'xml:space':          {inherit: true, initial: 'default', css: 'white-space', values: {'preserve':'preserve', 'default':'default', 'pre':'preserve', 'pre-line':'preserve', 'pre-wrap':'preserve', 'nowrap': 'default'}},
      'marker-start':       {inherit: true, initial: 'none'},
      'marker-mid':         {inherit: true, initial: 'none'},
      'marker-end':         {inherit: true, initial: 'none'},
      'opacity':            {inherit: false, initial: 1},
      'transform':          {inherit: false, initial: [1, 0, 0, 1, 0, 0]},
      'display':            {inherit: false, initial: 'inline', values: {'none':'none', 'inline':'inline', 'block':'inline'}},
      'clip-path':          {inherit: false, initial: 'none'},
      'mask':               {inherit: false, initial: 'none'},
      'overflow':           {inherit: false, initial: 'hidden', values: {'hidden':'hidden', 'scroll':'hidden', 'visible':'visible'}},
      'vector-effect':      {inherit: true, initial: 'none', values: {'none':'none', 'non-scaling-stroke':'non-scaling-stroke'}}
    };

    function docBeginGroup(bbox) {
      let group = new (function PDFGroup() {})();
      group.name = 'G' + (doc._groupCount = (doc._groupCount || 0) + 1);
      group.resources = doc.ref();
      group.xobj = doc.ref({
        Type: 'XObject',
        Subtype: 'Form',
        FormType: 1,
        BBox: bbox,
        Group: {S: 'Transparency', CS: 'DeviceRGB', I: true, K: false},
        Resources: group.resources
      });
      group.xobj.write('');
      group.savedMatrix = doc._ctm;
      group.savedPage = doc.page;
      groupStack.push(group);
      doc._ctm = [1, 0, 0, 1, 0, 0];
      doc.page = {
        width: doc.page.width, height: doc.page.height,
        write: function(data) {group.xobj.write(data);},
        fonts: {}, xobjects: {}, ext_gstates: {}, patterns: {}
      };
      return group;
    }
    function docEndGroup(group) {
      if (group !== groupStack.pop()) {throw('Group not matching');}
      if (Object.keys(doc.page.fonts).length) {group.resources.data.Font = doc.page.fonts;}
      if (Object.keys(doc.page.xobjects).length) {group.resources.data.XObject = doc.page.xobjects;}
      if (Object.keys(doc.page.ext_gstates).length) {group.resources.data.ExtGState = doc.page.ext_gstates;}
      if (Object.keys(doc.page.patterns).length) {group.resources.data.Pattern = doc.page.patterns;}
      group.resources.end();
      group.xobj.end();
      doc._ctm = group.savedMatrix;
      doc.page = group.savedPage;
    }
    function docInsertGroup(group) {
      doc.page.xobjects[group.name] = group.xobj;
      doc.addContent('/' + group.name + ' Do');
    }
    function docApplyMask(group, clip) {
      let name = 'M' + (doc._maskCount = (doc._maskCount || 0) + 1);
      let gstate = doc.ref({
        Type: 'ExtGState', CA: 1, ca: 1, BM: 'Normal',
        SMask: {S: 'Luminosity', G: group.xobj, BC: (clip ? [0, 0, 0] : [1, 1, 1])}
      });
      gstate.end();
      doc.page.ext_gstates[name] = gstate;
      doc.addContent('/' + name + ' gs');
    }
    function docCreatePattern(group, dx, dy, matrix) {
      let pattern = {
        type: 'PDFPattern',
        group: group,
        dx: dx,
        dy: dy,
        matrix: matrix || [1, 0, 0, 1, 0, 0],
      }
      return pattern;
    }
    function docUsePattern(pattern, stroke) {
      let name = 'P' + (doc._patternCount = (doc._patternCount || 0) + 1);
      let ref = doc.ref({
        Type: 'Pattern', PatternType: 1, PaintType: 1, TilingType: 2,
        BBox: [0, 0, pattern.dx, pattern.dy], XStep: pattern.dx, YStep: pattern.dy,
        Matrix: multiplyMatrix(doc._ctm, pattern.matrix),
        Resources: {
          ProcSet: ['PDF', 'Text', 'ImageB', 'ImageC', 'ImageI'],
          XObject: (function() {let temp = {}; temp[pattern.group.name] = pattern.group.xobj; return temp;})()
        }
      });
      ref.write('/' + pattern.group.name + ' Do');
      ref.end();
      doc.page.patterns[name] = ref;
      if (stroke) {
        doc.addContent('/Pattern CS');
        doc.addContent('/' + name + ' SCN');
      } else {
        doc.addContent('/Pattern cs');
        doc.addContent('/' + name + ' scn');
      }
    }
    function docBeginText(font, size) {
      if (!doc.page.fonts[font.id]) {doc.page.fonts[font.id] = font.ref();}
      doc.addContent('BT').addContent('/' + font.id + ' ' + size + ' Tf');
    }
    function docSetTextMatrix(a, b, c, d, e, f) {
      doc.addContent(validateNumber(a) + ' ' + validateNumber(b) + ' ' + validateNumber(-c) + ' '  + validateNumber(-d) + ' ' + validateNumber(e) + ' ' + validateNumber(f) + ' Tm');
    }
    function docSetTextMode(fill, stroke) {
      let mode = fill && stroke ? 2 : stroke ? 1 : fill ? 0 : 3;
      doc.addContent(mode + ' Tr');
    }
    function docWriteGlyphs(positions, font) {
      let commands = [];
      let commandStr = '';
      const skew = font.fauxItalic ? -0.25 : 0;

      // Add the given character to the 'TJ' command string.
      function addChar(char) {
        commandStr += char.glyph;
        if (char.kern === 0) return;
        commands.push(`<${commandStr}> ${validateNumber(char.kern)}`);
        commandStr = '';
      };

      // Flush the current TJ command string to the output stream.
      function flush() {
        if (commandStr.length) {
          commands.push(`<${commandStr}> 0`);
          commandStr = '';
        }
        if (commands.length) {
          doc.addContent(`[${commands.join(' ')}] TJ`);
          commands = [];
        }
      };

      for (let i = 0; i < positions.length; i++) {
        const pos = positions[i];

        if (pos.hidden || isEqual(pos.width, 0)) {
          flush();
          continue;
        }

        if (pos.continuous) {
          addChar(pos);
          continue;
        }

        // If this character is non-continuous, flush the command buffer.
        flush();

        // Start a new TJ command after writing a Text Matrix (Tm)
        const cos = Math.cos(pos.rotate);
        const sin = Math.sin(pos.rotate);
        docSetTextMatrix(cos * pos.scale, sin * pos.scale, cos * skew - sin, sin * skew + cos, pos.x, pos.y);
        addChar(pos);
      };

      // Flush any remaining characters in the buffer.
      flush();
    }
    function docEndText() {
      doc.addContent('ET');
    }
    function docFillColor(color) {
      if (color[0].type === 'PDFPattern') {
        doc.fillOpacity(color[1]);
        docUsePattern(color[0], false);
      } else {
        doc.fillColor(color[0], color[1]);
      }
    }
    function docStrokeColor(color) {
      if (color[0].type === 'PDFPattern') {
        doc.strokeOpacity(color[1]);
        docUsePattern(color[0], true);
      } else {
        doc.strokeColor(color[0], color[1]);
      }
    }
    // PDFKit doesn't accept any 0s in the dash array, but that's perfectly
    // valid in SVG. So this function applys a dash array and offset, detecting
    // any 0s in the dash array and updating it and the dash offset as needed to
    // remove the zeros, but preserve the end result.
    //
    // `dashArray` must have an even number of elements
    function docApplyDash(dashArray, dashOffset) {
      let index;
      // Anytime there's a 0 that isn't the first or last element of the array,
      // we can remove it by combining the previous or next value. If it's a
      // dash, then it's a zero-length dash between two spaces, so the dash can
      // be eliminated and spaces combined by summing them, replacing all three
      // values with the sum of the two spaces. If the 0 value is a space, then
      // it's a zero-length space between two dashes, and the dashes can be
      // similarly combined. So first we run that logic iteratively to remove
      // all the 0s from the dash array that aren't the first or last element.
      // Note that because we replace 3 values with one value, this doesn't
      // change the even-ness of the length of dashArray.
      while ((index = dashArray.slice(1, -1).indexOf(0)) !== -1) {
        let actualIndex = index + 1;
        let replacementValue = dashArray[actualIndex - 1] + dashArray[actualIndex + 1];
        dashArray = dashArray
          .slice(0, actualIndex - 1)
          .concat([replacementValue])
          .concat(dashArray.slice(actualIndex + 2));
      }

      // The stroke array only having two elements (a dash value and space
      // value) is a special case.
      if (dashArray.length === 2) {
        if (dashArray[0] === 0) {
          // Regardless of the space value, the dash length is zero, so we're
          // not actually drawing a stroke. We can't describe that in a
          // doc.dash() call in a way that PDFKit will accept, so we set the
          // stroke opacity to zero as our best approximation.
          doc.strokeOpacity(0);
          return;
        } else if (dashArray[1] === 0) {
          // Regardless of the dash value, the space value is zero, meaning
          // we're actually drawing a solid stroke, not a dashed one. We can
          // make this happen by just emptying out the dash array.
          dashArray = [];
        }
      } else {
        if (dashArray[0] === 0) {
          // The first dash is zero-length. We fix this by combining the first
          // space (just after the first dash) with the last space and updating
          // the dash offset accordingly. For example, if we had
          //
          // [ 0 4 3 2 5 1 ] (dash offset 0)
          //
          // ␣␣␣␣---␣␣-----␣
          // ⎸
          //
          // we'd end up with
          //
          // [ 3 2 5 5 ] (dash offset -4)
          //
          // ---␣␣-----␣␣␣␣␣
          //            ⎸
          //
          // Another example where the dash array also ends with a 0:
          //
          // [ 0 4 3 2 5 0 ] (dash offset 0)
          //
          // ␣␣␣␣---␣␣-----
          // ⎸
          //
          // we'd end up with
          //
          // [ 3 2 5 4 ] (dash offset -4)
          //
          // ---␣␣-----␣␣␣␣
          //           ⎸
          dashOffset -= dashArray[1];
          dashArray[dashArray.length - 1] += dashArray[1];
          dashArray = dashArray.slice(2);
        }
        if (dashArray[dashArray.length - 1] === 0) {
          // The last space is zero-length. We fix this by combining the last dash
          // (just before the last space) with the first dash and updating the
          // dash offset accordingly. For example, if we had
          //
          // [ 1 4 3 2 5 0 ] (dash offset 0)
          //
          // -␣␣␣␣---␣␣-----
          // ⎸
          //
          // we'd end up with
          //
          // [ 6 4 3 2 ] (dash offset 5)
          //
          // ------␣␣␣␣---␣␣
          //      ⎸
          //
          dashOffset += dashArray[dashArray.length - 2];
          dashArray[0] += dashArray[dashArray.length - 2];
          dashArray = dashArray.slice(0, -2);
        }  
      }

      // Ensure the dash offset is non-negative (because of crbug.com/660850).
      // First compute the total length of the dash array so we can add it to
      // dash offset until dash offset is non-negative.
      let length = 0;
      for (let i = 0; i < dashArray.length; i++) {length += dashArray[i];}
      if (length > 0) {
        while (dashOffset < 0) {
          dashOffset += length;
        }
      }

      doc.dash(dashArray, {phase: dashOffset});
    }
    function docInsertLink(x, y, w, h, url) {
      let ref = doc.ref({
        Type: 'Annot',
        Subtype: 'Link',
        Rect: [x, y, w, h],
        Border: [0, 0, 0],
        A: {
          S: 'URI',
          URI: new String(url)
        }
      });
      ref.end();
      links.push(ref);
    }
    function parseXml(xml) {
      let SvgNode = function(tag, type, value, error) {
        this.error = error;
        this.nodeName = tag;
        this.nodeValue = value;
        this.nodeType = type;
        this.attributes = Object.create(null);
        this.childNodes = [];
        this.parentNode = null;
        this.id = '';
        this.textContent = '';
        this.classList = [];
      };
      SvgNode.prototype.getAttribute = function(attr) {
        return this.attributes[attr] != null ? this.attributes[attr] : null;
      };
      SvgNode.prototype.getElementById = function(id) {
        let result = null;
        (function recursive(node) {
          if (result) {return;}
          if (node.nodeType === 1) {
            if (node.id === id) {result = node;}
            for (let i = 0; i < node.childNodes.length; i++) {
              recursive(node.childNodes[i]);
            }
          }
        })(this);
        return result;
      };
      SvgNode.prototype.getElementsByTagName = function(tag) {
        let result = [];
        (function recursive(node) {
          if (node.nodeType === 1) {
            if (node.nodeName === tag) {result.push(node);}
            for (let i = 0; i < node.childNodes.length; i++) {
              recursive(node.childNodes[i]);
            }
          }
        })(this);
        return result;
      };
      let parser = new StringParser(xml.trim()), result, child, error = false; 
      let recursive = function() {
        let temp, child;
        if (temp = parser.match(/^<([\w:.-]+)\s*/, true)) { // Opening tag
          let node = new SvgNode(temp[1], 1, null, error);
          while (temp = parser.match(/^([\w:.-]+)(?:\s*=\s*"([^"]*)"|\s*=\s*'([^']*)')?\s*/, true)) { // Attribute
            let attr = temp[1], value = decodeEntities(temp[2] || temp[3] || '');
            if (!node.attributes[attr]) {
              node.attributes[attr] = value;
              if (attr === 'id') {node.id = value;}
              if (attr === 'class') {node.classList = value.split(' ');}
            } else {
              warningCallback('parseXml: duplicate attribute "' + attr + '"');
              error = true;
            }
          }
          if (parser.match(/^>/)) { // End of opening tag
            while (child = recursive()) {
              node.childNodes.push(child);
              child.parentNode = node;
              node.textContent += (child.nodeType === 3 || child.nodeType === 4 ? child.nodeValue : child.textContent);
            }
            if (temp = parser.match(/^<\/([\w:.-]+)\s*>/, true)) { // Closing tag
              if (temp[1] === node.nodeName) {
                return node;
              } else {
                warningCallback('parseXml: tag not matching, opening "' + node.nodeName + '" & closing "' + temp[1] + '"');
                error = true;
                return node;
              }
            } else {
              warningCallback('parseXml: tag not matching, opening "' + node.nodeName + '" & not closing');
              error = true;
              return node;
            }
          } else if (parser.match(/^\/>/)) { // Self-closing tag
            return node;
          } else {
            warningCallback('parseXml: tag could not be parsed "' + node.nodeName + '"');
            error = true;
          }
        } else if (temp = parser.match(/^<!--[\s\S]*?-->/)) { // Comment
          return new SvgNode(null, 8, temp, error);
        } else if (temp = parser.match(/^<\?[\s\S]*?\?>/)) { // Processing instructions
          return new SvgNode(null, 7, temp, error);
        } else if (temp = parser.match(/^<!DOCTYPE\s*([\s\S]*?)>/)) { // Doctype
          return new SvgNode(null, 10, temp, error);
        } else if (temp = parser.match(/^<!\[CDATA\[([\s\S]*?)\]\]>/, true)) { // Cdata node
          return new SvgNode('#cdata-section', 4, temp[1], error);
        } else if (temp = parser.match(/^([^<]+)/, true)) { // Text node
          return new SvgNode('#text', 3, decodeEntities(temp[1]), error);
        }
      };
      while (child = recursive()) {
        if (child.nodeType === 1 && !result) {
          result = child;
        } else if (child.nodeType === 1 || (child.nodeType === 3 && child.nodeValue.trim() !== '')) {
          warningCallback('parseXml: data after document end has been discarded');
        }
      }
      if (parser.matchAll()) {
        warningCallback('parseXml: parsing error');
      }
      return result;
    };
    function decodeEntities(str) {
      return(str.replace(/&(?:#([0-9]+)|#[xX]([0-9A-Fa-f]+)|([0-9A-Za-z]+));/g, function(mt, m0, m1, m2) {
        if (m0) {return String.fromCharCode(parseInt(m0, 10));}
        else if (m1) {return String.fromCharCode(parseInt(m1, 16));}
        else if (m2 && Entities[m2]) {return String.fromCharCode(Entities[m2]);}
        else {return mt;}
      }));
    }
    function parseColor(raw) {
      let temp, result;
      raw = (raw || '').trim();
      if (temp = NamedColors[raw]) {
        result = [temp.slice(), 1];
      } else if (temp = raw.match(/^cmyk\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.]+)\s*\)$/i)) {
        temp[1] = parseInt(temp[1]); temp[2] = parseInt(temp[2]); temp[3] = parseInt(temp[3]); temp[4] = parseFloat(temp[4]);
        if (temp[1] <= 100 && temp[2] <= 100 && temp[3] <= 100 && temp[4] <= 100) {
          result = [temp.slice(1, 5), 1];
        }
      } else if (temp = raw.match(/^rgba\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.]+)\s*\)$/i)) {
        temp[1] = parseInt(temp[1]); temp[2] = parseInt(temp[2]); temp[3] = parseInt(temp[3]); temp[4] = parseFloat(temp[4]);
        if (temp[1] < 256 && temp[2] < 256 && temp[3] < 256 && temp[4] <= 1) {
          result = [temp.slice(1, 4), temp[4]];
        }
      } else if (temp = raw.match(/^rgb\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)$/i)) {
        temp[1] = parseInt(temp[1]); temp[2] = parseInt(temp[2]); temp[3] = parseInt(temp[3]);
        if (temp[1] < 256 && temp[2] < 256 && temp[3] < 256) {
          result = [temp.slice(1, 4), 1];
        }
      } else if (temp = raw.match(/^rgb\(\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/i)) {
        temp[1] = 2.55 * parseFloat(temp[1]); temp[2] = 2.55 * parseFloat(temp[2]); temp[3] = 2.55 * parseFloat(temp[3]);
        if (temp[1] < 256 && temp[2] < 256 && temp[3] < 256) {
          result = [temp.slice(1, 4), 1];
        }
      } else if (temp = raw.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)) {
        result = [[parseInt(temp[1], 16), parseInt(temp[2], 16), parseInt(temp[3], 16)], 1];
      } else if (temp = raw.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i)) {
        result = [[0x11 * parseInt(temp[1], 16), 0x11 * parseInt(temp[2], 16), 0x11 * parseInt(temp[3], 16)], 1];
      }
      return colorCallback ? colorCallback(result, raw) : result;
    }
    function opacityToColor(color, opacity, isMask) {
      let newColor = color[0].slice(),
          newOpacity = color[1] * opacity;
      if (isMask) {
        for (let i = 0; i < color.length; i++) {
          newColor[i] *= newOpacity;
        }
        return [newColor, 1];
      } else {
        return [newColor, newOpacity];
      }
    }
    function multiplyMatrix() {
      function multiply(a, b) {
        return [ a[0]*b[0]+a[2]*b[1], a[1]*b[0]+a[3]*b[1], a[0]*b[2]+a[2]*b[3],
                 a[1]*b[2]+a[3]*b[3], a[0]*b[4]+a[2]*b[5]+a[4], a[1]*b[4]+a[3]*b[5]+a[5] ];
      }
      let result = arguments[0];
      for (let i = 1; i < arguments.length; i++) {
        result = multiply(result, arguments[i]);
      }
      return result;
    }
    function transformPoint(p, m) {
      return [m[0] * p[0] + m[2] * p[1] + m[4], m[1] * p[0] + m[3] * p[1] + m[5]];
    }
    function getGlobalMatrix() {
      let ctm = doc._ctm;
      for (let i = groupStack.length - 1; i >= 0; i--) {
        ctm = multiplyMatrix(groupStack[i].savedMatrix, ctm);
      }
      return ctm;
    }
    function getPageBBox() {
      return new SvgShape().M(0, 0).L(doc.page.width, 0).L(doc.page.width, doc.page.height).L(0, doc.page.height)
                           .transform(inverseMatrix(getGlobalMatrix())).getBoundingBox();
    }
    function getPageScale() {
      const bbox = getPageBBox();
      const width = doc.page.width;
      return width / bbox[2];
    }
    function inverseMatrix(m) {
      let dt = m[0] * m[3] - m[1] * m[2];
      return [m[3] / dt, -m[1] / dt, -m[2] / dt, m[0] / dt, (m[2]*m[5] - m[3]*m[4]) / dt, (m[1]*m[4] - m[0]*m[5]) / dt];
    }
    function validateMatrix(m) {
      let m0 = validateNumber(m[0]), m1 = validateNumber(m[1]), m2 = validateNumber(m[2]),
          m3 = validateNumber(m[3]), m4 = validateNumber(m[4]), m5 = validateNumber(m[5]);
      if (isNotEqual(m0 * m3 - m1 * m2, 0)) {
        return [m0, m1, m2, m3, m4, m5];
      }
    }
    function solveEquation(curve) {
      let a = curve[2] || 0, b = curve[1] || 0, c = curve[0] || 0;
      if (isEqual(a, 0) && isEqual(b, 0)) {
        return [];
      } else if (isEqual(a, 0)) {
        return [(-c) / b];
      } else {
        let d = b * b - 4 * a * c;
        if (isNotEqual(d, 0) && d > 0) {
          return [(-b + Math.sqrt(d)) / (2 * a), (-b - Math.sqrt(d)) / (2 * a)];
        } else if (isEqual(d, 0)) {
          return [(-b) / (2 * a)];
        } else {
          return [];
        }
      }
    }
    function getCurveValue(t, curve) {
      return (curve[0] || 0) + (curve[1] || 0) * t + (curve[2] || 0) * t * t + (curve[3] || 0) * t * t * t;
    }
    function isEqual(number, ref) {
      return Math.abs(number - ref) < 1e-10;
    }
    function isNotEqual(number, ref) {
      return Math.abs(number - ref) >= 1e-10;
    }
    function validateNumber(n) {
      return n > -1e21 && n < 1e21 ? Math.round(n * 1e6) / 1e6 : 0;
    }
    function isArrayLike(v) {
      return typeof v === 'object' && v !== null && typeof v.length === 'number';
    }
    function parseTranform(v) {
      let parser = new StringParser((v || '').trim()), result = [1, 0, 0, 1, 0, 0], temp;
      while (temp = parser.match(/^([A-Za-z]+)\s*[(]([^(]+)[)]/, true)) {
        let func = temp[1], nums = [], parser2 = new StringParser(temp[2].trim()), temp2;
        while (temp2 = parser2.matchNumber()) {
          nums.push(Number(temp2));
          parser2.matchSeparator();
        }
        if (func === 'matrix' && nums.length === 6) {
          result = multiplyMatrix(result, [nums[0], nums[1], nums[2], nums[3], nums[4], nums[5]]);
        } else if (func === 'translate' && nums.length === 2) {
          result = multiplyMatrix(result, [1, 0, 0, 1, nums[0], nums[1]]);
        } else if (func === 'translate' && nums.length === 1) {
          result = multiplyMatrix(result, [1, 0, 0, 1, nums[0], 0]);
        } else if (func === 'scale' && nums.length === 2) {
          result = multiplyMatrix(result, [nums[0], 0, 0, nums[1], 0, 0]);
        } else if (func === 'scale' && nums.length === 1) {
          result = multiplyMatrix(result, [nums[0], 0, 0, nums[0], 0, 0]);
        } else if (func === 'rotate' && nums.length === 3) {
          let a = nums[0] * Math.PI / 180;
          result = multiplyMatrix(result, [1, 0, 0, 1, nums[1], nums[2]], [Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0], [1, 0, 0, 1, -nums[1], -nums[2]]);
        } else if (func === 'rotate' && nums.length === 1) {
          let a = nums[0] * Math.PI / 180;
          result = multiplyMatrix(result, [Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);
        } else if (func === 'skewX' && nums.length === 1) {
          let a = nums[0] * Math.PI / 180;
          result = multiplyMatrix(result, [1, 0, Math.tan(a), 1, 0, 0]);
        } else if (func === 'skewY' && nums.length === 1) {
          let a = nums[0] * Math.PI / 180;
          result = multiplyMatrix(result, [1, Math.tan(a), 0, 1, 0, 0]);
        } else {return;}
        parser.matchSeparator();
      }
      if (parser.matchAll()) {return;}
      return result;
    }
    function parseAspectRatio(aspectRatio, availWidth, availHeight, elemWidth, elemHeight, initAlign) {
      let temp = (aspectRatio || '').trim().match(/^(none)$|^x(Min|Mid|Max)Y(Min|Mid|Max)(?:\s+(meet|slice))?$/) || [],
          ratioType = temp[1] || temp[4] || 'meet',
          xAlign = temp[2] || 'Mid',
          yAlign = temp[3] || 'Mid',
          scaleX = availWidth / elemWidth,
          scaleY = availHeight / elemHeight,
          dx = {'Min':0, 'Mid':0.5, 'Max':1}[xAlign] - (initAlign || 0),
          dy = {'Min':0, 'Mid':0.5, 'Max':1}[yAlign] - (initAlign || 0);
      if (ratioType === 'slice') {
        scaleY = scaleX = Math.max(scaleX, scaleY);
      } else if (ratioType === 'meet') {
        scaleY = scaleX = Math.min(scaleX, scaleY);
      }
      return [scaleX, 0, 0, scaleY, dx * (availWidth - elemWidth * scaleX), dy * (availHeight - elemHeight * scaleY)];
    }
    function parseStyleAttr(v) {
      let result = Object.create(null);
      v = (v || '').trim().split(/;/);
      for (let i = 0; i < v.length; i++) {
        let key = (v[i].split(':')[0] || '').trim(),
            value = (v[i].split(':')[1] || '').trim();
        if (key) {
          result[key] = value;
        }
      }
      if (result['marker']) {
        if (!result['marker-start']) {result['marker-start'] = result['marker'];}
        if (!result['marker-mid']) {result['marker-mid'] = result['marker'];}
        if (!result['marker-end']) {result['marker-end'] = result['marker'];}
      }
      if (result['font']) {
        let fontFamily = null, fontSize = null, fontStyle = "normal", fontWeight = "normal", fontVariant = "normal";
        let parts = result['font'].split(/\s+/);
        for (let i = 0; i < parts.length; i++) {
          switch (parts[i]) {
            case "normal":
              break;
            case "italic": case "oblique":
              fontStyle = parts[i];
              break;
            case "small-caps":
              fontVariant = parts[i];
              break;
            case "bold": case "bolder": case "lighter": case "100": case "200": case "300":
            case "400": case "500": case "600": case "700": case "800": case "900":
              fontWeight = parts[i];
              break;
            default:
              if (!fontSize) {
                fontSize = parts[i].split('/')[0];
              } else {
                if (!fontFamily) {
                  fontFamily = parts[i];
                } else {
                  fontFamily += ' ' + parts[i];
                }
              }
              break;
          }
        }
        if (!result['font-style']) {result['font-style'] = fontStyle;}
        if (!result['font-variant']) {result['font-variant'] = fontVariant;}
        if (!result['font-weight']) {result['font-weight'] = fontWeight;}
        if (!result['font-size']) {result['font-size'] = fontSize;}
        if (!result['font-family']) {result['font-family'] = fontFamily;}
      }
      return result;
    }
    function parseSelector(v) {
      let parts = v.split(/(?=[.#])/g), ids = [], classes = [], tags = [], temp;
      for (let i = 0; i < parts.length; i++) {
        if (temp = parts[i].match(/^[#]([_A-Za-z0-9-]+)$/)) {
          ids.push(temp[1]);
        } else if (temp = parts[i].match(/^[.]([_A-Za-z0-9-]+)$/)) {
          classes.push(temp[1]);
        } else if (temp = parts[i].match(/^([_A-Za-z0-9-]+)$/)) {
          tags.push(temp[1]);
        } else if (parts[i] !== '*') {
          return;
        }
      }
      return {
        tags: tags, ids: ids, classes: classes,
        specificity: ids.length * 10000 + classes.length * 100 + tags.length
      };
    }
    function parseStyleSheet(v) {
      let parser = new StringParser(v.trim()), rules = [], rule;
      while (rule = parser.match(/^\s*([^\{\}]*?)\s*\{([^\{\}]*?)\}/, true)) {
        let selectors = rule[1].split(/\s*,\s*/g),
            css = parseStyleAttr(rule[2]);
        for (let i = 0; i < selectors.length; i++) {
          let selector = parseSelector(selectors[i]);
          if (selector) {
            rules.push({selector: selector, css:css});
          }
        }
      }
      return rules;
    }
    function matchesSelector(elem, selector) {
      if (elem.nodeType !== 1) {return false;}
      for (let i = 0; i < selector.tags.length; i++) {
        if (selector.tags[i] !== elem.nodeName) {return false;}
      }
      for (let i = 0; i < selector.ids.length; i++) {
        if (selector.ids[i] !== elem.id) {return false;}
      }
      for (let i = 0; i < selector.classes.length; i++) {
        if (elem.classList.indexOf(selector.classes[i]) === -1) {return false;}
      }
      return true;
    }
    function getStyle(elem) {
      let result = Object.create(null);
      let specificities = Object.create(null);
      for (let i = 0; i < styleRules.length; i++) {
        let rule = styleRules[i];
        if (matchesSelector(elem, rule.selector)) {
          for (let key in rule.css) {
            if (!(specificities[key] > rule.selector.specificity)) {
              result[key] = rule.css[key];
              specificities[key] = rule.selector.specificity;
            }
          }
        }
      }
      return result;
    }
    function combineArrays(array1, array2) {
      return array1.concat(array2.slice(array1.length));
    }
    function getAscent(font, size) {
      return Math.max(font.ascender, (font.bbox[3] || font.bbox.maxY) * (font.scale || 1)) * size / 1000;
    }
    function getDescent(font, size) {
      return Math.min(font.descender, (font.bbox[1] || font.bbox.minY) * (font.scale || 1)) * size / 1000;
    }
    function getXHeight(font, size) {
      return (font.xHeight || 0.5 * (font.ascender - font.descender)) * size / 1000;
    }
    function getBaseline(font, size, baseline, shift) {
      let dy1, dy2;
      switch (baseline) {
        case 'middle': dy1 = 0.5 * getXHeight(font, size); break;
        case 'central': dy1 = 0.5 * (getDescent(font, size) + getAscent(font, size)); break;
        case 'after-edge': case 'text-after-edge': dy1 = getDescent(font, size); break;
        case 'alphabetic': case 'auto': case 'baseline': dy1 = 0; break;
        case 'mathematical': dy1 = 0.5 * getAscent(font, size); break;
        case 'hanging': dy1 = 0.8 * getAscent(font, size); break;
        case 'before-edge': case 'text-before-edge': dy1 = getAscent(font, size); break;
        default: dy1 = 0; break;
      }
      switch (shift) {
        case 'baseline': dy2 = 0; break;
        case 'super': dy2 = 0.6 * size; break;
        case 'sub': dy2 = -0.6 * size; break;
        default: dy2 = shift; break;
      }
      return dy1 - dy2;
    }
    function getTextPos(font, size, text) {
      let encoded = font.encode('' + text), hex = encoded[0], pos = encoded[1], data = [];
      for (let i = 0; i < hex.length; i++) {
        let unicode = font.unicode ? font.unicode[parseInt(hex[i], 16)] : [text.charCodeAt(i)];
        data.push({
          glyph: hex[i],
          unicode: unicode,
          kern: pos[i].advanceWidth - pos[i].xAdvance,
          width: pos[i].advanceWidth * size / 1000,
          xOffset: pos[i].xOffset * size / 1000,
          yOffset: pos[i].yOffset * size / 1000,
          xAdvance: pos[i].xAdvance * size / 1000,
          yAdvance: pos[i].yAdvance * size / 1000
        });
      }
      return data;
    }
    function createSVGElement(obj, inherits) {
      switch (obj.nodeName) {
        case 'use': return new SvgElemUse(obj, inherits);
        case 'symbol': return new SvgElemSymbol(obj, inherits);
        case 'g': return new SvgElemGroup(obj, inherits);
        case 'a': return new SvgElemLink(obj, inherits);
        case 'svg': return new SvgElemSvg(obj, inherits);
        case 'image': return new SVGElemImage(obj, inherits);
        case 'rect': return new SvgElemRect(obj, inherits);
        case 'circle': return new SvgElemCircle(obj, inherits);
        case 'ellipse': return new SvgElemEllipse(obj, inherits);
        case 'line': return new SvgElemLine(obj, inherits);
        case 'polyline': return new SvgElemPolyline(obj, inherits);
        case 'polygon': return new SvgElemPolygon(obj, inherits);
        case 'path': return new SvgElemPath(obj, inherits);
        case 'text': return new SvgElemText(obj, inherits);
        case 'tspan': return new SvgElemTspan(obj, inherits);
        case 'textPath': return new SvgElemTextPath(obj, inherits);
        case '#text': case '#cdata-section': return new SvgElemTextNode(obj, inherits);
        default: return new SvgElem(obj, inherits);
      }
    }

    var StringParser = function(str) {
      this.match = function(exp, all) {
        let temp = str.match(exp);
        if (!temp || temp.index !== 0) {return;}
        str = str.substring(temp[0].length);
        return (all ? temp : temp[0]);
      };
      this.matchSeparator = function() {
        return this.match(/^(?:\s*,\s*|\s*|)/);
      };
      this.matchSpace = function() {
        return this.match(/^(?:\s*)/);
      };
      this.matchLengthUnit = function() {
        return this.match(/^(?:px|pt|cm|mm|in|pc|em|ex|%|)/);
      };
      this.matchNumber = function() {
        return this.match(/^(?:[-+]?(?:[0-9]+[.][0-9]+|[0-9]+[.]|[.][0-9]+|[0-9]+)(?:[eE][-+]?[0-9]+)?)/);
      };
      this.matchAll = function() {
        return this.match(/^[\s\S]+/);
      };
    };

    var BezierSegment = function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
      let divisions = 6 * precision;
      let equationX = [p1x, -3 * p1x + 3 * c1x, 3 * p1x - 6 * c1x + 3 * c2x, -p1x + 3 * c1x - 3 * c2x + p2x];
      let equationY = [p1y, -3 * p1y + 3 * c1y, 3 * p1y - 6 * c1y + 3 * c2y, -p1y + 3 * c1y - 3 * c2y + p2y];
      let derivativeX = [-3 * p1x + 3 * c1x, 6 * p1x - 12 * c1x + 6 * c2x, -3 * p1x + 9 * c1x - 9 * c2x + 3 * p2x];
      let derivativeY = [-3 * p1y + 3 * c1y, 6 * p1y - 12 * c1y + 6 * c2y, -3 * p1y + 9 * c1y - 9 * c2y + 3 * p2y];
      let lengthMap = [0];
      for (let i = 1; i <= divisions; i++) {
        let t = (i - 0.5) / divisions;
        let dx = getCurveValue(t, derivativeX) / divisions,
            dy = getCurveValue(t, derivativeY) / divisions,
            l = Math.sqrt(dx * dx + dy * dy);
        lengthMap[i] = lengthMap[i - 1] + l;
      }
      this.totalLength = lengthMap[divisions];
      this.startPoint = [p1x, p1y, isEqual(p1x, c1x) && isEqual(p1y, c1y) ? Math.atan2(c2y - c1y, c2x - c1x) : Math.atan2(c1y - p1y, c1x - p1x)];
      this.endPoint = [p2x, p2y, isEqual(c2x, p2x) && isEqual(c2y, p2y) ? Math.atan2(c2y - c1y, c2x - c1x) : Math.atan2(p2y - c2y, p2x - c2x)];
      this.getBoundingBox = function() {
        let temp;
        let minX = getCurveValue(0, equationX), minY = getCurveValue(0, equationY),
            maxX = getCurveValue(1, equationX), maxY = getCurveValue(1, equationY);
        if (minX > maxX) {temp = maxX; maxX = minX; minX = temp;}
        if (minY > maxY) {temp = maxY; maxY = minY; minY = temp;}
        let rootsX = solveEquation(derivativeX);
        for (let i = 0; i < rootsX.length; i++) {
          if (rootsX[i] >= 0 && rootsX[i] <= 1) {
            let x = getCurveValue(rootsX[i], equationX);
            if (x < minX) {minX = x;}
            if (x > maxX) {maxX = x;}
          }
        }
        let rootsY = solveEquation(derivativeY);
        for (let i = 0; i < rootsY.length; i++) {
          if (rootsY[i] >= 0 && rootsY[i] <= 1) {
            let y = getCurveValue(rootsY[i], equationY);
            if (y < minY) {minY = y;}
            if (y > maxY) {maxY = y;}
          }
        }
        return [minX, minY, maxX, maxY];
      };
      this.getPointAtLength = function(l) {
        if (isEqual(l, 0)) {return this.startPoint;}
        if (isEqual(l, this.totalLength)) {return this.endPoint;}
        if (l < 0 || l > this.totalLength) {return;}
        for (let i = 1; i <= divisions; i++) {
          let l1 = lengthMap[i-1], l2 = lengthMap[i];
          if (l1 <= l && l <= l2) {
            let t = (i - (l2 - l) / (l2 - l1)) / divisions,
                x = getCurveValue(t, equationX), y = getCurveValue(t, equationY),
                dx = getCurveValue(t, derivativeX), dy = getCurveValue(t, derivativeY);
            return [x, y, Math.atan2(dy, dx)];
          }
        }
      };
    };

    var LineSegment = function(p1x, p1y, p2x, p2y) {
      this.totalLength = Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y));
      this.startPoint = [p1x, p1y, Math.atan2(p2y - p1y, p2x - p1x)];
      this.endPoint = [p2x, p2y, Math.atan2(p2y - p1y, p2x - p1x)];
      this.getBoundingBox = function() {
        return [Math.min(this.startPoint[0], this.endPoint[0]), Math.min(this.startPoint[1], this.endPoint[1]),
                Math.max(this.startPoint[0], this.endPoint[0]), Math.max(this.startPoint[1], this.endPoint[1])];
      };
      this.getPointAtLength = function(l) {
        if (l >= 0 && l <= this.totalLength) {
          let r = l / this.totalLength || 0,
              x = this.startPoint[0] + r * (this.endPoint[0] - this.startPoint[0]),
              y = this.startPoint[1] + r * (this.endPoint[1] - this.startPoint[1]);
          return [x, y, this.startPoint[2]];
        }
      };
    };

    var SvgShape = function() {
      this.pathCommands = [];
      this.pathSegments = [];
      this.startPoint = null;
      this.endPoint = null;
      this.totalLength = 0;
      let startX = 0, startY = 0, currX = 0, currY = 0, lastCom, lastCtrlX, lastCtrlY;
      this.move = function(x, y) {
        startX = currX = x; startY = currY = y;
        return null;
      };
      this.line = function(x, y) {
        let segment = new LineSegment(currX, currY, x, y);
        currX = x; currY = y;
        return segment;
      };
      this.curve = function(c1x, c1y, c2x, c2y, x, y) {
        let segment = new BezierSegment(currX, currY, c1x, c1y, c2x, c2y, x, y);
        currX = x; currY = y;
        return segment;
      };
      this.close = function() {
        let segment = new LineSegment(currX, currY, startX, startY);
        currX = startX; currY = startY;
        return segment;
      };
      this.addCommand = function(data) {
        this.pathCommands.push(data);
        let segment = this[data[0]].apply(this, data.slice(3));
        if (segment) {
          segment.hasStart = data[1];
          segment.hasEnd = data[2];
          this.startPoint = this.startPoint || segment.startPoint;
          this.endPoint = segment.endPoint;
          this.pathSegments.push(segment);
          this.totalLength += segment.totalLength;
        }
      };
      this.M = function(x, y) {
        this.addCommand(['move', true, true, x, y]);
        lastCom = 'M';
        return this;
      };
      this.m = function(x, y) {
        return this.M(currX + x, currY + y);
      };
      this.Z = this.z = function() {
        this.addCommand(['close', true, true]);
        lastCom = 'Z';
        return this;
      };
      this.L = function(x, y) {
        this.addCommand(['line', true, true, x, y]);
        lastCom = 'L';
        return this;
      };
      this.l = function(x, y) {
        return this.L(currX + x, currY + y);
      };
      this.H = function(x) {
        return this.L(x, currY);
      };
      this.h = function(x) {
        return this.L(currX + x, currY);
      };
      this.V = function(y) {
        return this.L(currX, y);
      };
      this.v = function(y) {
        return this.L(currX, currY + y);
      };
      this.C = function(c1x, c1y, c2x, c2y, x, y) {
        this.addCommand(['curve', true, true, c1x, c1y, c2x, c2y, x, y]);
        lastCom = 'C'; lastCtrlX = c2x; lastCtrlY = c2y;
        return this;
      };
      this.c = function(c1x, c1y, c2x, c2y, x, y) {
        return this.C(currX + c1x, currY + c1y, currX + c2x, currY + c2y, currX + x, currY + y);
      };
      this.S = function(c1x, c1y, x, y) {
        return this.C(currX + (lastCom === 'C' ? currX - lastCtrlX : 0), currY + (lastCom === 'C' ? currY - lastCtrlY : 0), c1x, c1y, x, y);
      };
      this.s = function(c1x, c1y, x, y) {
        return this.C(currX + (lastCom === 'C' ? currX - lastCtrlX : 0), currY + (lastCom === 'C' ? currY - lastCtrlY : 0), currX + c1x, currY + c1y, currX + x, currY + y);
      };
      this.Q = function(cx, cy, x, y) {
        let c1x = currX + 2 / 3 * (cx - currX), c1y = currY + 2 / 3 * (cy - currY),
            c2x = x + 2 / 3 * (cx - x), c2y = y + 2 / 3 * (cy - y);
        this.addCommand(['curve', true, true, c1x, c1y, c2x, c2y, x, y]);
        lastCom = 'Q'; lastCtrlX = cx; lastCtrlY = cy;
        return this;
      };
      this.q = function(c1x, c1y, x, y) {
        return this.Q(currX + c1x, currY + c1y, currX + x, currY + y);
      };
      this.T = function(x, y) {
        return this.Q(currX + (lastCom === 'Q' ? currX - lastCtrlX : 0), currY + (lastCom === 'Q' ? currY - lastCtrlY : 0), x, y);
      };
      this.t = function(x, y) {
        return this.Q(currX + (lastCom === 'Q' ? currX - lastCtrlX : 0), currY + (lastCom === 'Q' ? currY - lastCtrlY : 0), currX + x, currY + y);
      };
      this.A = function(rx, ry, fi, fa, fs, x, y) {
        if (isEqual(rx, 0) || isEqual(ry, 0)) {
          this.addCommand(['line', true, true, x, y]);
        } else {
          fi = fi * (Math.PI / 180);
          rx = Math.abs(rx);
          ry = Math.abs(ry);
          fa = 1 * !!fa;
          fs = 1 * !!fs;
          let x1 = Math.cos(fi) * (currX - x) / 2 + Math.sin(fi) * (currY - y) / 2,
              y1 = Math.cos(fi) * (currY - y) / 2 - Math.sin(fi) * (currX - x) / 2,
              lambda = (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry);
          if (lambda > 1) {
            rx *= Math.sqrt(lambda);
            ry *= Math.sqrt(lambda);
          }
          let r = Math.sqrt(Math.max(0, rx * rx * ry * ry - rx * rx * y1 * y1 - ry * ry * x1 * x1) / (rx * rx * y1 * y1 + ry * ry * x1 * x1)),
              x2 = (fa === fs ? -1 : 1) * r * rx * y1 / ry,
              y2 = (fa === fs ? 1 : -1) * r * ry * x1 / rx;
          let cx = Math.cos(fi) * x2 - Math.sin(fi) * y2 + (currX + x) / 2,
              cy = Math.sin(fi) * x2 + Math.cos(fi) * y2 + (currY + y) / 2,
              th1 = Math.atan2((y1 - y2) / ry, (x1 - x2) / rx),
              th2 = Math.atan2((-y1 - y2) / ry, (-x1 - x2) / rx);
          if (fs === 0 && th2 - th1 > 0) {
            th2 -= 2 * Math.PI;
          } else if (fs === 1 && th2 - th1 < 0) {
            th2 += 2 * Math.PI;
          }
          let segms = Math.ceil(Math.abs(th2 - th1) / (Math.PI / precision));
          for (let i = 0; i < segms; i++) {
            let th3 = th1 + i * (th2 - th1) / segms,
                th4 = th1 + (i + 1) * (th2 - th1) / segms,
                t = 4/3 * Math.tan((th4 - th3) / 4);
            let c1x = cx + Math.cos(fi) * rx * (Math.cos(th3) - t * Math.sin(th3)) - Math.sin(fi) * ry * (Math.sin(th3) + t * Math.cos(th3)),
                c1y = cy + Math.sin(fi) * rx * (Math.cos(th3) - t * Math.sin(th3)) + Math.cos(fi) * ry * (Math.sin(th3) + t * Math.cos(th3)),
                c2x = cx + Math.cos(fi) * rx * (Math.cos(th4) + t * Math.sin(th4)) - Math.sin(fi) * ry * (Math.sin(th4) - t * Math.cos(th4)),
                c2y = cy + Math.sin(fi) * rx * (Math.cos(th4) + t * Math.sin(th4)) + Math.cos(fi) * ry * (Math.sin(th4) - t * Math.cos(th4)),
                endX = cx + Math.cos(fi) * rx * Math.cos(th4) - Math.sin(fi) * ry * Math.sin(th4),
                endY = cy + Math.sin(fi) * rx * Math.cos(th4) + Math.cos(fi) * ry * Math.sin(th4);
            this.addCommand(['curve', (i === 0), (i === segms - 1), c1x, c1y, c2x, c2y, endX, endY]);
          }
        }
        lastCom = 'A';
        return this;
      };
      this.a = function(rx, ry, fi, fa, fs, x, y) {
        return this.A(rx, ry, fi, fa, fs, currX + x, currY + y);
      };
      this.path = function(d) {
        let command, value, temp,
            parser = new StringParser((d || '').trim());
        while (command = parser.match(/^[astvzqmhlcASTVZQMHLC]/)) {
          parser.matchSeparator();
          let values = [];
          while (value = (PathFlags[command + values.length] ? parser.match(/^[01]/) : parser.matchNumber())) {
            parser.matchSeparator();
            if (values.length === PathArguments[command]) {
              this[command].apply(this, values);
              values = [];
              if (command === 'M') {command = 'L';}
              else if (command === 'm') {command = 'l';}
            }
            values.push(Number(value));
          }
          if (values.length === PathArguments[command]) {
            this[command].apply(this, values);
          } else {
            warningCallback('SvgPath: command ' + command + ' with ' + values.length + ' numbers'); return;
          }
        }
        if (temp = parser.matchAll()) {
          warningCallback('SvgPath: unexpected string ' + temp);
        }
        return this;
      };
      this.getBoundingBox = function() {
        let bbox = [Infinity, Infinity, -Infinity, -Infinity];
        function addBounds(bbox1) {
          if (bbox1[0] < bbox[0]) {bbox[0] = bbox1[0];}
          if (bbox1[2] > bbox[2]) {bbox[2] = bbox1[2];}
          if (bbox1[1] < bbox[1]) {bbox[1] = bbox1[1];}
          if (bbox1[3] > bbox[3]) {bbox[3] = bbox1[3];}
        }
        for (let i = 0; i < this.pathSegments.length; i++) {
          addBounds(this.pathSegments[i].getBoundingBox());
        }
        if (bbox[0] === Infinity) {bbox[0] = 0;}
        if (bbox[1] === Infinity) {bbox[1] = 0;}
        if (bbox[2] === -Infinity) {bbox[2] = 0;}
        if (bbox[3] === -Infinity) {bbox[3] = 0;}
        return bbox;
      };
      this.getPointAtLength = function(l) {
        if (l >= 0 && l <= this.totalLength) {
          let temp;
          for (let i = 0; i < this.pathSegments.length; i++) {
            if (temp = this.pathSegments[i].getPointAtLength(l)) {
              return temp;
            }
            l -= this.pathSegments[i].totalLength;
          }
          return this.endPoint;
        }
      };
      this.transform = function(m) {
        this.pathSegments = [];
        this.startPoint = null;
        this.endPoint = null;
        this.totalLength = 0;
        for (let i = 0; i < this.pathCommands.length; i++) {
          let data = this.pathCommands.shift();
          for (let j = 3; j < data.length; j+=2) {
            let p = transformPoint([data[j], data[j + 1]], m)
            data[j] = p[0];
            data[j + 1] = p[1];
          }
          this.addCommand(data);
        }
        return this;        
      };
      this.mergeShape = function(shape) {
        for (let i = 0; i < shape.pathCommands.length; i++) {
          this.addCommand(shape.pathCommands[i].slice());
        }
        return this;
      };
      this.clone = function() {
        return new SvgShape().mergeShape(this);
      };
      this.insertInDocument = function() {
        for (let i = 0; i < this.pathCommands.length; i++) {
          let command = this.pathCommands[i][0], values = this.pathCommands[i].slice(3);
          switch(command) {
            case 'move':  doc.moveTo(values[0], values[1]);  break;
            case 'line':  doc.lineTo(values[0], values[1]);  break;
            case 'curve':  doc.bezierCurveTo(values[0], values[1], values[2], values[3], values[4], values[5]);  break;
            case 'close':  doc.closePath();  break;
          }
        }
      };
      this.getSubPaths = function() {
        let subPaths = [], shape = new SvgShape();
        for (let i = 0; i < this.pathCommands.length; i++) {
          let data = this.pathCommands[i], command = this.pathCommands[i][0];
          if (command === 'move' && i !== 0) {
            subPaths.push(shape);
            shape = new SvgShape();
          }
          shape.addCommand(data);
        }
        subPaths.push(shape);
        return subPaths;
      };
      this.getMarkers = function() {
        let markers = [], subPaths = this.getSubPaths();
        for (let i = 0; i < subPaths.length; i++) {
          let subPath = subPaths[i], subPathMarkers = [];
          for (let j = 0; j < subPath.pathSegments.length; j++) {
            let segment = subPath.pathSegments[j];
            if (isNotEqual(segment.totalLength, 0) || j === 0 || j === subPath.pathSegments.length - 1) {
              if (segment.hasStart) {
                let startMarker = segment.getPointAtLength(0), prevEndMarker = subPathMarkers.pop();
                if (prevEndMarker) {startMarker[2] = 0.5 * (prevEndMarker[2] + startMarker[2]);}
                subPathMarkers.push(startMarker);
              }
              if (segment.hasEnd) {
                let endMarker = segment.getPointAtLength(segment.totalLength);
                subPathMarkers.push(endMarker);
              }
            }
          }
          markers = markers.concat(subPathMarkers);
        }
        return markers;
      };
    };

    var SvgElem = function(obj, inherits) {
      let styleCache = Object.create(null);
      let childrenCache = null;
      this.name = obj.nodeName;
      this.isOuterElement = obj === svg || !obj.parentNode;
      this.inherits = inherits || (!this.isOuterElement ? createSVGElement(obj.parentNode, null) : null);
      this.stack = (this.inherits ? this.inherits.stack.concat(obj) : [obj]);
      this.style = parseStyleAttr(typeof obj.getAttribute === 'function' && obj.getAttribute('style'));
      this.css = useCSS ? getComputedStyle(obj) : getStyle(obj);
      this.allowedChildren = [];
      this.attr = function(key) {
        if (typeof obj.getAttribute === 'function') {
          return obj.getAttribute(key);
        }
      };
      this.resolveUrl = function(value) {
        let temp = (value || '').match(/^\s*(?:url\("(.*)#(.*)"\)|url\('(.*)#(.*)'\)|url\((.*)#(.*)\)|(.*)#(.*))\s*$/) || [];
        let file = temp[1] || temp[3] || temp[5] || temp[7],
            id = temp[2] || temp[4] || temp[6] || temp[8];
        if (id) {
          if (!file) {
            let svgObj = svg.getElementById(id);
            if (svgObj) {
              if (this.stack.indexOf(svgObj) === -1) {
                return svgObj;
              } else {
                warningCallback('SVGtoPDF: loop of circular references for id "' + id + '"');
                return;
              }
            }
          }
          if (documentCallback) {
            let svgs = documentCache[file];
            if (!svgs) {
              svgs = documentCallback(file);
              if (!isArrayLike(svgs)) {svgs = [svgs];}
              for (let i = 0; i < svgs.length; i++) {
                if (typeof svgs[i] === 'string') {svgs[i] = parseXml(svgs[i]);}
              }
              documentCache[file] = svgs;
            }
            for (let i = 0; i < svgs.length; i++) {
              let svgObj = svgs[i].getElementById(id);
              if (svgObj) {
                if (this.stack.indexOf(svgObj) === -1) {
                  return svgObj;
                } else {
                  warningCallback('SVGtoPDF: loop of circular references for id "' + file + '#' + id + '"');
                  return;
                }
              }
            }
          }
        }
      };
      this.computeUnits = function(value, unit, percent, isFontSize) {
        if (unit === '%') {
          return parseFloat(value) / 100 * (isFontSize || percent != null ? percent : this.getViewport());
        } else if (unit === 'ex' || unit === 'em') {
          return value * {'em':1, 'ex':0.5}[unit] * (isFontSize ? percent : this.get('font-size'));
        } else {
          return value * {'':1, 'px':1, 'pt':96/72, 'cm':96/2.54, 'mm':96/25.4, 'in':96, 'pc':96/6}[unit];
        }
      };
      this.computeLength = function(value, percent, initial, isFontSize) {
        let parser = new StringParser((value || '').trim()), temp1, temp2;
        if (typeof (temp1 = parser.matchNumber()) === 'string' && typeof (temp2 = parser.matchLengthUnit()) === 'string' && !parser.matchAll()) {
          return this.computeUnits(temp1, temp2, percent, isFontSize);
        }
        return initial;
      };
      this.computeLengthList = function(value, percent, strict) {
        let parser = new StringParser((value || '').trim()), result = [], temp1, temp2;
        while (typeof (temp1 = parser.matchNumber()) === 'string' && typeof (temp2 = parser.matchLengthUnit()) === 'string') {
          result.push(this.computeUnits(temp1, temp2, percent));
          parser.matchSeparator();
        }
        if (strict && parser.matchAll()) {return;}
        return result;
      };
      this.getLength = function(key, percent, initial) {
        return this.computeLength(this.attr(key), percent, initial);
      };
      this.getLengthList = function(key, percent) {
        return this.computeLengthList(this.attr(key), percent);
      };
      this.getUrl = function(key) {
        return this.resolveUrl(this.attr(key))
      };
      this.getNumberList = function(key) {
        let parser = new StringParser((this.attr(key) || '').trim()), result = [], temp;
        while (temp = parser.matchNumber()) {
          result.push(Number(temp));
          parser.matchSeparator();
        }
        result.error = parser.matchAll();
        return result;
      }
      this.getViewbox = function(key, initial) {
        let viewBox = this.getNumberList(key);
        if (viewBox.length === 4 && viewBox[2] >= 0 && viewBox[3] >= 0) {return viewBox;}
        return initial;
      };
      this.getPercent = function(key, initial) {
        let value = this.attr(key);
        let parser = new StringParser((value || '').trim()), temp1, temp2;
        let number = parser.matchNumber();
        if (!number) {return initial;}
        if (parser.match('%')) {number *= 0.01;}
        if (parser.matchAll()) {return initial;}
        return Math.max(0, Math.min(1, number));
      };
      this.chooseValue = function(args) {
        for (let i = 0; i < arguments.length; i++) {
          if (arguments[i] != null && arguments[i] === arguments[i]) {return arguments[i];}
        }
        return arguments[arguments.length - 1];
      };
      this.get = function(key) {
        if (styleCache[key] !== undefined) {return styleCache[key];}
        let keyInfo = Properties[key] || {}, value, result;
        for (let i = 0; i < 3; i++) {
          switch (i) {
            case 0:
              if (key !== 'transform') { // the CSS transform behaves strangely
                value = this.css[keyInfo.css || key];
              }
              break;
            case 1:
              value = this.style[key];
              break;
            case 2:
              value = this.attr(key);
              break;
          }
          if (value === 'inherit') {
            result = (this.inherits ? this.inherits.get(key) : keyInfo.initial);
            if (result != null) {return styleCache[key] = result;}
          }
          if (keyInfo.values != null) {
            result = keyInfo.values[value];
            if (result != null) {return styleCache[key] = result;}
          }
          if (value != null) {
            let parsed;
            switch (key) {
              case 'font-size':
                result = this.computeLength(value, this.inherits ? this.inherits.get(key) : keyInfo.initial, undefined, true);
                break;
              case 'baseline-shift':
                result = this.computeLength(value, this.get('font-size'));
                break;
              case 'font-family':
                result = value || undefined;
                break;
              case 'opacity': case 'stroke-opacity': case 'fill-opacity': case 'stop-opacity':
                parsed = parseFloat(value);
                if (!isNaN(parsed)) {
                  result = Math.max(0, Math.min(1, parsed));
                }
                break;
              case 'transform':
                result = parseTranform(value);
                break;
              case 'stroke-dasharray':
                if (value === 'none') {
                  result = [];
                } else if (parsed = this.computeLengthList(value, this.getViewport(), true)) {
                  let sum = 0, error = false;
                  for (let j = 0; j < parsed.length; j++) {
                    if (parsed[j] < 0) {error = true;}
                    sum += parsed[j];
                  }
                  if (!error) {
                    if (parsed.length % 2 === 1) {
                      parsed = parsed.concat(parsed);
                    }
                    result = (sum === 0 ? [] : parsed);
                  }
                }
                break;
              case 'color':
                if (value === 'none' || value === 'transparent') {
                  result = 'none';
                } else {
                  result = parseColor(value);
                }
                break;
              case 'fill': case 'stroke':
                if (value === 'none' || value === 'transparent') {
                  result = 'none';
                } else if (value === 'currentColor') {
                  result = this.get('color');
                } else if (parsed = parseColor(value)) {
                  return parsed;
                } else if (parsed = (value || '').split(' ')) {
                  let object = this.resolveUrl(parsed[0]),
                      fallbackColor = parseColor(parsed[1]);
                  if (object == null) {
                    result = fallbackColor;
                  } else if (object.nodeName === 'linearGradient' || object.nodeName === 'radialGradient') {
                    result = new SvgElemGradient(object, null, fallbackColor);
                  } else if (object.nodeName === 'pattern') {
                    result = new SvgElemPattern(object, null, fallbackColor);
                  } else {
                    result = fallbackColor;
                  }
                }
                break;
              case 'stop-color':
                if (value === 'none' || value === 'transparent') {
                  result = 'none';
                } else if (value === 'currentColor') {
                  result = this.get('color');
                } else {
                  result = parseColor(value);
                }
                break;
              case 'marker-start': case 'marker-mid': case 'marker-end': case 'clip-path': case 'mask':
                if (value === 'none') {
                  result = 'none';
                } else {
                  result = this.resolveUrl(value);
                }
                break;
              case 'stroke-width':
                parsed = this.computeLength(value, this.getViewport());
                if (parsed != null && parsed >= 0) {
                  result = parsed;
                }
                break;
              case 'stroke-miterlimit':
                parsed = parseFloat(value);
                if (parsed != null && parsed >= 1) {
                  result = parsed;
                }
                break;
              case 'word-spacing': case 'letter-spacing':
                result = this.computeLength(value, this.getViewport());
                break;
              case 'stroke-dashoffset':
                result = this.computeLength(value, this.getViewport());
                break;
            }
            if (result != null) {return styleCache[key] = result;}
          }
        }
        return styleCache[key] = (keyInfo.inherit && this.inherits ? this.inherits.get(key) : keyInfo.initial);
      };
      this.getChildren = function() {
        if (childrenCache != null) {return childrenCache;}
        let children = [];
        for (let i = 0; i < obj.childNodes.length; i++) {
          let child = obj.childNodes[i];
          if (!child.error && this.allowedChildren.indexOf(child.nodeName) !== -1) {
            children.push(createSVGElement(child, this));
          }
        }
        return childrenCache = children;
      };
      this.getParentVWidth = function() {
        return (this.inherits ? this.inherits.getVWidth(): viewportWidth);
      };
      this.getParentVHeight = function() {
        return (this.inherits ? this.inherits.getVHeight() : viewportHeight);
      };
      this.getParentViewport = function() {
        return Math.sqrt(0.5 * this.getParentVWidth() * this.getParentVWidth() + 0.5 * this.getParentVHeight() * this.getParentVHeight());
      };
      this.getVWidth = function() {
        return this.getParentVWidth();
      };
      this.getVHeight = function() {
        return this.getParentVHeight();
      };
      this.getViewport = function() {
        return Math.sqrt(0.5 * this.getVWidth() * this.getVWidth() + 0.5 * this.getVHeight() * this.getVHeight());
      };
      this.getBoundingBox = function() {
        let shape = this.getBoundingShape();
        return shape.getBoundingBox();
      };
    };

    var SvgElemStylable = function(obj, inherits) {
      SvgElem.call(this, obj, inherits);
      this.transform = function() {
        doc.transform.apply(doc, this.getTransformation());
      };
      this.clip = function() {
        if (this.get('clip-path') !== 'none') {
          let clipPath = new SvgElemClipPath(this.get('clip-path'), null);
          clipPath.useMask((clipPath.attr('clipPathUnits') === 'objectBoundingBox') ? this.getBoundingBox() : null);
          return true;
        }
      };
      this.mask = function() {
        if (this.get('mask') !== 'none') {
          let mask = new SvgElemMask(this.get('mask'), null);
          mask.useMask(this.getBoundingBox());
          return true;
        }
      };
      this.getFill = function(isClip, isMask) {
        let opacity = this.get('opacity'),
            fill = this.get('fill'),
            fillOpacity = this.get('fill-opacity');
        if (isClip) {return DefaultColors.white;}
        if (fill !== 'none' && opacity && fillOpacity) {
          if (fill instanceof SvgElemGradient || fill instanceof SvgElemPattern) {
            return fill.getPaint(this.getBoundingBox(), fillOpacity * opacity, isClip, isMask);
          }
          return opacityToColor(fill, fillOpacity * opacity, isMask);
        }
      };
      this.getStroke = function(isClip, isMask) {
        let opacity = this.get('opacity'),
            stroke = this.get('stroke'),
            strokeOpacity = this.get('stroke-opacity');
        if (isClip || isEqual(this.get('stroke-width'), 0)) {return;}
        if (stroke !== 'none' && opacity && strokeOpacity) {
          if (stroke instanceof SvgElemGradient || stroke instanceof SvgElemPattern) {
            return stroke.getPaint(this.getBoundingBox(), strokeOpacity * opacity, isClip, isMask);
          }
          return opacityToColor(stroke, strokeOpacity * opacity, isMask);
        }
      };
    };

    var SvgElemHasChildren = function(obj, inherits) {
      SvgElemStylable.call(this, obj, inherits);
      this.allowedChildren = ['use', 'g', 'a', 'svg', 'image', 'rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path', 'text'];
      this.getBoundingShape = function() {
        let shape = new SvgShape(),
            children = this.getChildren();
        for (let i = 0; i < children.length; i++) {
          if (children[i].get('display') !== 'none') {
            if (typeof children[i].getBoundingShape === 'function') {
              let childShape = children[i].getBoundingShape().clone();
              if (typeof children[i].getTransformation === 'function') {
                childShape.transform(children[i].getTransformation());
              }
              shape.mergeShape(childShape);
            }
          }
        }
        return shape;
      };
      this.drawChildren = function(isClip, isMask) {
        let children = this.getChildren();
        for (let i = 0; i < children.length; i++) {
          if (children[i].get('display') !== 'none') {
            if (typeof children[i].drawInDocument === 'function') {
              children[i].drawInDocument(isClip, isMask);
            }
          }
        }
      };
    };

    var SvgElemContainer = function(obj, inherits) {
      SvgElemHasChildren.call(this, obj, inherits);
      this.drawContent = function(isClip, isMask) {
        this.transform();
        let clipped = this.clip(),
            masked = this.mask(),
            group;
        if ((this.get('opacity') < 1 || clipped || masked) && !isClip) {
          group = docBeginGroup(getPageBBox());
        }
        this.drawChildren(isClip, isMask);
        if (group) {
          docEndGroup(group);
          doc.fillOpacity(this.get('opacity'));
          docInsertGroup(group);
        }
      };
    };

    var SvgElemUse = function(obj, inherits) {
      SvgElemContainer.call(this, obj, inherits);
      let x = this.getLength('x', this.getVWidth(), 0),
          y = this.getLength('y', this.getVHeight(), 0),
          child = this.getUrl('href') || this.getUrl('xlink:href');
      if (child) {child = createSVGElement(child, this);}
      this.getChildren  = function() {
        return child ? [child] : [];
      };
      this.drawInDocument = function(isClip, isMask) {
        doc.save();
        this.drawContent(isClip, isMask);
        doc.restore();
      };
      this.getTransformation = function() {
        return multiplyMatrix(this.get('transform'), [1, 0, 0, 1, x, y]);
      };
    };

    var SvgElemSymbol = function(obj, inherits) {
      SvgElemContainer.call(this, obj, inherits);
      let width = this.getLength('width', this.getParentVWidth(), this.getParentVWidth()),
          height = this.getLength('height', this.getParentVHeight(), this.getParentVHeight());
      if (inherits instanceof SvgElemUse) {
        width = inherits.getLength('width', inherits.getParentVWidth(), width);
        height = inherits.getLength('height', inherits.getParentVHeight(), height);
      }
      let aspectRatio = (this.attr('preserveAspectRatio') || '').trim(),
          viewBox = this.getViewbox('viewBox', [0, 0, width, height]);
      this.getVWidth = function() {
        return viewBox[2];
      };
      this.getVHeight = function() {
        return viewBox[3];
      };
      this.drawInDocument = function(isClip, isMask) {
        doc.save();
        this.drawContent(isClip, isMask);
        doc.restore();
      };
      this.getTransformation = function() {
        return multiplyMatrix(parseAspectRatio(aspectRatio, width, height, viewBox[2], viewBox[3]), [1, 0, 0, 1, -viewBox[0], -viewBox[1]]);
      };
    };

    var SvgElemGroup = function(obj, inherits) {
      SvgElemContainer.call(this, obj, inherits);
      this.drawInDocument = function(isClip, isMask) {
        doc.save();
        if (this.link && !isClip && !isMask) {this.addLink();}
        this.drawContent(isClip, isMask);
        doc.restore();
      };
      this.getTransformation = function() {
        return this.get('transform');
      };
    };

    var SvgElemLink = function(obj, inherits) {
      if (inherits && inherits.isText) {
        SvgElemTspan.call(this, obj, inherits);
        this.allowedChildren = ['textPath', 'tspan', '#text', '#cdata-section', 'a'];
      } else {
        SvgElemGroup.call(this, obj, inherits);
      }
      this.link = this.attr('href') || this.attr('xlink:href');
      this.addLink = function() {
        if (this.link.match(/^(?:[a-z][a-z0-9+.-]*:|\/\/)?/i) && this.getChildren().length) {
          let bbox = this.getBoundingShape().transform(getGlobalMatrix()).getBoundingBox();
          docInsertLink(bbox[0], bbox[1], bbox[2], bbox[3], this.link);
        }
      }
    };

    var SvgElemSvg = function(obj, inherits) {
      SvgElemContainer.call(this, obj, inherits);
      let width = this.getLength('width', this.getParentVWidth(), this.getParentVWidth()),
          height = this.getLength('height', this.getParentVHeight(), this.getParentVHeight()),
          x = this.getLength('x', this.getParentVWidth(), 0),
          y = this.getLength('y', this.getParentVHeight(), 0);
      if (inherits instanceof SvgElemUse) {
        width = inherits.getLength('width', inherits.getParentVWidth(), width);
        height = inherits.getLength('height', inherits.getParentVHeight(), height);
      }
      let aspectRatio = this.attr('preserveAspectRatio'),
          viewBox = this.getViewbox('viewBox', [0, 0, width, height]);
      if (this.isOuterElement && preserveAspectRatio) {
        x = y = 0;
        width = viewportWidth;
        height = viewportHeight;
        aspectRatio = preserveAspectRatio;
      }
      this.getVWidth = function() {
        return viewBox[2];
      };
      this.getVHeight = function() {
        return viewBox[3];
      };
      this.drawInDocument = function(isClip, isMask) {
        doc.save();
        if (this.get('overflow') === 'hidden') {
          new SvgShape().M(x, y).L(x + width, y).L(x + width, y + height).L(x, y + height).Z()
                        .transform(this.get('transform'))
                        .insertInDocument();
          doc.clip();
        }
        this.drawContent(isClip, isMask);
        doc.restore();
      };
      this.getTransformation = function() {
        return multiplyMatrix(
          this.get('transform'),
          [1, 0, 0, 1, x, y],
          parseAspectRatio(aspectRatio, width, height, viewBox[2], viewBox[3]),
          [1, 0, 0, 1, -viewBox[0], -viewBox[1]]
        );
      };
    };

    var SVGElemImage = function(obj, inherits) {
      SvgElemStylable.call(this, obj, inherits);
      let link = imageCallback(this.attr('href') || this.attr('xlink:href') || ''),
          x = this.getLength('x', this.getVWidth(), 0),
          y = this.getLength('y', this.getVHeight(), 0),
          width = this.getLength('width', this.getVWidth(), 'auto'),
          height = this.getLength('height', this.getVHeight(), 'auto'),
          image;
      try {
        image = doc.openImage(link);
      } catch(e) {
        warningCallback('SVGElemImage: failed to open image "' + link + '" in PDFKit');
      }
      if (image) {
        if (width === 'auto' && height !== 'auto') {
          width = height * image.width / image.height;
        } else if (height === 'auto' && width !== 'auto') {
          height = width * image.height / image.width;
        } else if (width === 'auto' && height === 'auto') {
          width = image.width;
          height = image.height;
        }
      }
      if (width === 'auto' || width < 0) {width = 0;}
      if (height === 'auto' || height < 0) {height = 0;}
      this.getTransformation = function() {
        return this.get('transform');
      };
      this.getBoundingShape = function() {
        return new SvgShape().M(x, y).L(x + width, y).M(x + width, y + height).L(x, y + height);
      };
      this.drawInDocument = function(isClip, isMask) {
        if (this.get('visibility') === 'hidden' || !image) {return;}
        doc.save();
        this.transform();
        if (this.get('overflow') === 'hidden') {
          doc.rect(x, y, width, height).clip();
        }
        this.clip();
        this.mask();
        doc.translate(x, y);
        doc.transform.apply(doc, parseAspectRatio(this.attr('preserveAspectRatio'), width, height, image ? image.width : width, image ? image.height : height));
        if (!isClip) {
          doc.fillOpacity(this.get('opacity'));
          doc.image(image, 0, 0);
        } else {
          doc.rect(0, 0, image.width, image.height);
          docFillColor(DefaultColors.white).fill();
        }
        doc.restore();
      };
    };

    var SvgElemPattern = function(obj, inherits, fallback) {
      SvgElemHasChildren.call(this, obj, inherits);
      this.ref = (function() {
        let ref = this.getUrl('href') || this.getUrl('xlink:href');
        if (ref && ref.nodeName === obj.nodeName) {
          return new SvgElemPattern(ref, inherits, fallback);
        }
      }).call(this);
      let _attr = this.attr;
      this.attr = function(key) {
        let attr = _attr.call(this, key);
        if (attr != null || key === 'href' || key === 'xlink:href') {return attr;}
        return this.ref ? this.ref.attr(key) : null;
      };
      let _getChildren = this.getChildren;
      this.getChildren = function() {
        let children = _getChildren.call(this);
        if (children.length > 0) {return children;}
        return this.ref ? this.ref.getChildren() : [];
      };
      this.getPaint = function(bBox, gOpacity, isClip, isMask) {
        let bBoxUnitsPattern = (this.attr('patternUnits') !== 'userSpaceOnUse'),
            bBoxUnitsContent = (this.attr('patternContentUnits') === 'objectBoundingBox'),
            x = this.getLength('x', (bBoxUnitsPattern ? 1 : this.getParentVWidth()), 0),
            y = this.getLength('y', (bBoxUnitsPattern ? 1 : this.getParentVHeight()), 0),
            width = this.getLength('width', (bBoxUnitsPattern ? 1 : this.getParentVWidth()), 0),
            height = this.getLength('height', (bBoxUnitsPattern ? 1 : this.getParentVHeight()), 0);
        if (bBoxUnitsContent && !bBoxUnitsPattern) { // Use the same units for pattern & pattern content
          x = (x - bBox[0]) / (bBox[2] - bBox[0]) || 0;
          y = (y - bBox[1]) / (bBox[3] - bBox[1]) || 0;
          width = width / (bBox[2] - bBox[0]) || 0;
          height = height / (bBox[3] - bBox[1]) || 0;
        } else if (!bBoxUnitsContent && bBoxUnitsPattern) {
          x = bBox[0] + x * (bBox[2] - bBox[0]);
          y = bBox[1] + y * (bBox[3] - bBox[1]);
          width = width * (bBox[2] - bBox[0]);
          height = height * (bBox[3] - bBox[1]);
        }
        let viewBox = this.getViewbox('viewBox', [0, 0, width, height]),
            aspectRatio = (this.attr('preserveAspectRatio') || '').trim(),
            aspectRatioMatrix = multiplyMatrix(
              parseAspectRatio(aspectRatio, width, height, viewBox[2], viewBox[3], 0),
              [1, 0, 0, 1, -viewBox[0], -viewBox[1]]
            ),
            matrix = parseTranform(this.attr('patternTransform'));
        if (bBoxUnitsContent) {
          matrix = multiplyMatrix([bBox[2] - bBox[0], 0, 0, bBox[3] - bBox[1], bBox[0], bBox[1]], matrix);
        }
        matrix = multiplyMatrix(matrix, [1, 0, 0, 1, x, y]);
        if ((matrix = validateMatrix(matrix)) && (aspectRatioMatrix = validateMatrix(aspectRatioMatrix)) && (width = validateNumber(width)) && (height = validateNumber(height))) {
          let group = docBeginGroup([0, 0, width, height]);
          doc.transform.apply(doc, aspectRatioMatrix);
          this.drawChildren(isClip, isMask);
          docEndGroup(group);
          return [docCreatePattern(group, width, height, matrix), gOpacity];
        } else {
          return fallback ? [fallback[0], fallback[1] * gOpacity] : undefined;
        }
      };
      this.getVWidth = function() {
        let bBoxUnitsPattern = (this.attr('patternUnits') !== 'userSpaceOnUse'),
            width = this.getLength('width', (bBoxUnitsPattern ? 1 : this.getParentVWidth()), 0);
        return this.getViewbox('viewBox', [0, 0, width, 0])[2];
      };
      this.getVHeight = function() {
        let bBoxUnitsPattern = (this.attr('patternUnits') !== 'userSpaceOnUse'),
            height = this.getLength('height', (bBoxUnitsPattern ? 1 : this.getParentVHeight()), 0);
        return this.getViewbox('viewBox', [0, 0, 0, height])[3];
      };
    };

    var SvgElemGradient = function(obj, inherits, fallback) {
      SvgElem.call(this, obj, inherits);
      this.allowedChildren = ['stop'];
      this.ref = (function() {
        let ref = this.getUrl('href') || this.getUrl('xlink:href');
        if (ref && ref.nodeName === obj.nodeName) {
          return new SvgElemGradient(ref, inherits, fallback);
        }
      }).call(this);
      let _attr = this.attr;
      this.attr = function(key) {
        let attr = _attr.call(this, key);
        if (attr != null || key === 'href' || key === 'xlink:href') {return attr;}
        return this.ref ? this.ref.attr(key) : null;
      };
      let _getChildren = this.getChildren;
      this.getChildren = function() {
        let children = _getChildren.call(this);
        if (children.length > 0) {return children;}
        return this.ref ? this.ref.getChildren() : [];
      };
      this.getPaint = function(bBox, gOpacity, isClip, isMask) {
        let children = this.getChildren();
        if (children.length === 0) {return;}
        if (children.length === 1) {
          let child = children[0],
              stopColor = child.get('stop-color');
          if (stopColor === 'none') {return;}
          return opacityToColor(stopColor, child.get('stop-opacity') * gOpacity, isMask);
        }
        let bBoxUnits = (this.attr('gradientUnits') !== 'userSpaceOnUse'),
            matrix = parseTranform(this.attr('gradientTransform')),
            spread = this.attr('spreadMethod'),
            grad,
            x1, x2, y1, y2, r2,
            nAfter = 0,
            nBefore = 0,
            nTotal = 1;
        if (bBoxUnits) {
          matrix = multiplyMatrix([bBox[2] - bBox[0], 0, 0, bBox[3] - bBox[1], bBox[0], bBox[1]], matrix);
        }
        if (matrix = validateMatrix(matrix)) {
          if (this.name === 'linearGradient') {
            x1 = this.getLength('x1', (bBoxUnits ? 1 : this.getVWidth()), 0);
            x2 = this.getLength('x2', (bBoxUnits ? 1 : this.getVWidth()), (bBoxUnits ? 1 : this.getVWidth()));
            y1 = this.getLength('y1', (bBoxUnits ? 1 : this.getVHeight()), 0);
            y2 = this.getLength('y2', (bBoxUnits ? 1 : this.getVHeight()), 0);
          } else {
            x2 = this.getLength('cx', (bBoxUnits ? 1 : this.getVWidth()), (bBoxUnits ? 0.5 : 0.5 * this.getVWidth()));
            y2 = this.getLength('cy', (bBoxUnits ? 1 : this.getVHeight()), (bBoxUnits ? 0.5 : 0.5 * this.getVHeight()));
            r2 = this.getLength('r', (bBoxUnits ? 1 : this.getViewport()), (bBoxUnits ? 0.5 : 0.5 * this.getViewport()));
            x1 = this.getLength('fx', (bBoxUnits ? 1 : this.getVWidth()), x2);
            y1 = this.getLength('fy', (bBoxUnits ? 1 : this.getVHeight()), y2);
            if (r2 < 0) {
              warningCallback('SvgElemGradient: negative r value');
            }
            let d = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)),
                multiplier = 1;
            if (d > r2) { // according to specification
              multiplier = r2 / d;
              x1 = x2 + (x1 - x2) * multiplier;
              y1 = y2 + (y1 - y2) * multiplier;
            }
            r2 = Math.max(r2, d * multiplier * (1 + 1e-6)); // fix for edge-case gradients see issue #84
          }
          if (spread === 'reflect' || spread === 'repeat') {
            let inv = inverseMatrix(matrix),
                corner1 = transformPoint([bBox[0], bBox[1]], inv),
                corner2 = transformPoint([bBox[2], bBox[1]], inv),
                corner3 = transformPoint([bBox[2], bBox[3]], inv),
                corner4 = transformPoint([bBox[0], bBox[3]], inv);
            if (this.name === 'linearGradient') { // See file 'gradient-repeat-maths.png'
              nAfter  = Math.max((corner1[0] - x2) * (x2 - x1) + (corner1[1] - y2) * (y2 - y1),
                                 (corner2[0] - x2) * (x2 - x1) + (corner2[1] - y2) * (y2 - y1),
                                 (corner3[0] - x2) * (x2 - x1) + (corner3[1] - y2) * (y2 - y1),
                                 (corner4[0] - x2) * (x2 - x1) + (corner4[1] - y2) * (y2 - y1))
                                / (Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
              nBefore = Math.max((corner1[0] - x1) * (x1 - x2) + (corner1[1] - y1) * (y1 - y2),
                                 (corner2[0] - x1) * (x1 - x2) + (corner2[1] - y1) * (y1 - y2),
                                 (corner3[0] - x1) * (x1 - x2) + (corner3[1] - y1) * (y1 - y2),
                                 (corner4[0] - x1) * (x1 - x2) + (corner4[1] - y1) * (y1 - y2))
                                / (Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
            } else {
              nAfter  = Math.sqrt(Math.max(Math.pow(corner1[0] - x2, 2) + Math.pow(corner1[1] - y2, 2),
                                           Math.pow(corner2[0] - x2, 2) + Math.pow(corner2[1] - y2, 2),
                                           Math.pow(corner3[0] - x2, 2) + Math.pow(corner3[1] - y2, 2),
                                           Math.pow(corner4[0] - x2, 2) + Math.pow(corner4[1] - y2, 2))) / r2 - 1;
            }
            nAfter = Math.ceil(nAfter + 0.5); // Add a little more because the stroke can extend outside of the bounding box
            nBefore = Math.ceil(nBefore + 0.5);
            nTotal = nBefore + 1 + nAfter; // How many times the gradient needs to be repeated to fill the object bounding box
          }
          if (this.name === 'linearGradient') {
            grad = doc.linearGradient(x1 - nBefore * (x2 - x1), y1 - nBefore * (y2 - y1), x2 + nAfter * (x2 - x1), y2 + nAfter * (y2 - y1));
          } else {
            grad = doc.radialGradient(x1, y1, 0, x2, y2, r2 + nAfter * r2);
          }
          for (let n = 0; n < nTotal; n++) {
            let offset = 0,
                inOrder = (spread !== 'reflect' || (n - nBefore) % 2 === 0);
            for (let i = 0; i < children.length; i++) {
              let child = children[inOrder ? i : children.length - 1 - i],
                  stopColor = child.get('stop-color');
              if (stopColor === 'none') {stopColor = DefaultColors.transparent;}
              stopColor = opacityToColor(stopColor, child.get('stop-opacity') * gOpacity, isMask);
              offset = Math.max(offset, inOrder ? child.getPercent('offset', 0) : 1 - child.getPercent('offset', 0));
              if (i === 0 && stopColor[0].length === 4) {grad._colorSpace = 'DeviceCMYK';} // Fix until PR #763 is merged into PDFKit
              if (i === 0 && offset > 0) {
                grad.stop((n + 0) / nTotal, stopColor[0], stopColor[1]);
              }
              grad.stop((n + offset) / (nAfter + nBefore + 1), stopColor[0], stopColor[1]);
              if (i === children.length - 1 && offset < 1) {
                grad.stop((n + 1) / nTotal, stopColor[0], stopColor[1]);
              }
            }
          }
          grad.setTransform.apply(grad, matrix);
          return [grad, 1];
        } else {
          return fallback ? [fallback[0], fallback[1] * gOpacity] : undefined;
        }
      }
    };

    var SvgElemBasicShape = function(obj, inherits) {
      SvgElemStylable.call(this, obj, inherits);
      this.dashScale = 1;
      this.getBoundingShape = function() {
        return this.shape;
      };
      this.getTransformation = function() {
        return this.get('transform');
      };
      this.drawInDocument = function(isClip, isMask) {
        if (this.get('visibility') === 'hidden' || !this.shape) {return;}
        doc.save();
        if (this.get('vector-effect') === 'non-scaling-stroke') {
          this.shape.transform(this.getTransformation());
        } else {
          this.transform();
        }
        this.clip();
        if (!isClip) {
          let masked = this.mask(),
              group;
          if (masked) {
            group = docBeginGroup(getPageBBox());
          }
          let subPaths = this.shape.getSubPaths(),
              fill = this.getFill(isClip, isMask),
              stroke = this.getStroke(isClip, isMask),
              lineWidth = this.get('stroke-width'),
              lineCap = this.get('stroke-linecap');

          if (this.get('vector-effect') === 'non-scaling-stroke') {
            lineWidth = lineWidth / getPageScale();
          }
          
          if (fill || stroke) {
            if (fill) {
              docFillColor(fill);
            }
            if (stroke) {
              for (let j = 0; j < subPaths.length; j++) {
                if (isEqual(subPaths[j].totalLength, 0)) {
                  if ((lineCap === 'square' || lineCap === 'round') && lineWidth > 0) {
                    if (subPaths[j].startPoint && subPaths[j].startPoint.length > 1) {
                      let x = subPaths[j].startPoint[0],
                          y = subPaths[j].startPoint[1];
                      docFillColor(stroke);
                      if (lineCap === 'square') {
                        doc.rect(x - 0.5 * lineWidth, y - 0.5 * lineWidth, lineWidth, lineWidth);
                      } else if (lineCap === 'round') {
                        doc.circle(x, y, 0.5 * lineWidth);
                      }
                      doc.fill();
                    }
                  }
                }
              }
              let dashArray = this.get('stroke-dasharray'),
                  dashOffset = this.get('stroke-dashoffset');
              if (isNotEqual(this.dashScale, 1)) {
                for (let j = 0; j < dashArray.length; j++) {
                  dashArray[j] *= this.dashScale;
                }
                dashOffset *= this.dashScale;
              }
              docStrokeColor(stroke);
              doc.lineWidth(lineWidth)
                 .miterLimit(this.get('stroke-miterlimit'))
                 .lineJoin(this.get('stroke-linejoin'))
                 .lineCap(lineCap);
              docApplyDash(dashArray, dashOffset);
            }
            for (let j = 0; j < subPaths.length; j++) {
              if (subPaths[j].totalLength > 0) {
                subPaths[j].insertInDocument();
              }
            }
            if (fill && stroke) {
              doc.fillAndStroke(this.get('fill-rule'));
            } else if (fill) {
              doc.fill(this.get('fill-rule'));
            } else if (stroke) {
              doc.stroke();
            }
          }
          let markerStart = this.get('marker-start'),
              markerMid = this.get('marker-mid'),
              markerEnd = this.get('marker-end');
          if (markerStart !== 'none' || markerMid !== 'none' || markerEnd !== 'none') {
            let markersPos = this.shape.getMarkers();
            if (markerStart !== 'none' && markersPos.length > 0) {
              let marker = new SvgElemMarker(markerStart, null);
              marker.drawMarker(false, isMask, markersPos[0], lineWidth);
            }
            if (markerMid !== 'none') {
              for (let i = 1; i < markersPos.length - 1; i++) {
                let marker = new SvgElemMarker(markerMid, null);
                marker.drawMarker(false, isMask, markersPos[i], lineWidth);
              }
            }
            if (markerEnd !== 'none' && markersPos.length > 0) {
              let marker = new SvgElemMarker(markerEnd, null);
              marker.drawMarker(false, isMask, markersPos[markersPos.length - 1], lineWidth);
            }
          }
          if (group) {
            docEndGroup(group);
            docInsertGroup(group);
          }
        } else {
          this.shape.insertInDocument();
          docFillColor(DefaultColors.white);
          doc.fill(this.get('clip-rule'));
        }
        doc.restore();
      };
    };

    var SvgElemRect = function(obj, inherits) {
      SvgElemBasicShape.call(this, obj, inherits);
      let x = this.getLength('x', this.getVWidth(), 0),
          y = this.getLength('y', this.getVHeight(), 0),
          w = this.getLength('width', this.getVWidth(), 0),
          h = this.getLength('height', this.getVHeight(), 0),
          rx = this.getLength('rx', this.getVWidth()),
          ry = this.getLength('ry', this.getVHeight());
      if (rx === undefined && ry === undefined) {rx = ry = 0;}
      else if (rx === undefined && ry !== undefined) {rx = ry;}
      else if (rx !== undefined && ry === undefined) {ry = rx;}
      if (w > 0 && h > 0) {
        if (rx && ry) {
          rx = Math.min(rx, 0.5 * w);
          ry = Math.min(ry, 0.5 * h);
          this.shape = new SvgShape().M(x + rx, y).L(x + w - rx, y).A(rx, ry, 0, 0, 1, x + w, y + ry)
                            .L(x + w, y + h - ry).A(rx, ry, 0, 0, 1, x + w - rx, y + h).L(x + rx, y + h)
                            .A(rx, ry, 0, 0, 1, x, y + h - ry).L(x, y + ry).A(rx, ry, 0, 0, 1, x + rx, y).Z();
        } else {
          this.shape = new SvgShape().M(x, y).L(x + w, y).L(x + w, y + h).L(x, y + h).Z();
        }
      } else {
        this.shape = new SvgShape();
      }
    };

    var SvgElemCircle = function(obj, inherits) {
      SvgElemBasicShape.call(this, obj, inherits);
      let cx = this.getLength('cx', this.getVWidth(), 0),
          cy = this.getLength('cy', this.getVHeight(), 0),
          r = this.getLength('r', this.getViewport(), 0);
      if (r > 0) {
        this.shape = new SvgShape().M(cx + r, cy).A(r, r, 0, 0, 1, cx - r, cy).A(r, r, 0, 0, 1, cx + r, cy).Z();
      } else {
        this.shape = new SvgShape();
      }
    };

    var SvgElemEllipse = function(obj, inherits) {
      SvgElemBasicShape.call(this, obj, inherits);
      let cx = this.getLength('cx', this.getVWidth(), 0),
          cy = this.getLength('cy', this.getVHeight(), 0),
          rx = this.getLength('rx', this.getVWidth(), 0),
          ry = this.getLength('ry', this.getVHeight(), 0);
      if (rx > 0 && ry > 0) {
        this.shape = new SvgShape().M(cx + rx, cy).A(rx, ry, 0, 0, 1, cx - rx, cy).A(rx, ry, 0, 0, 1, cx + rx, cy).Z();
      } else {
        this.shape = new SvgShape();
      }
    };

    var SvgElemLine = function(obj, inherits) {
      SvgElemBasicShape.call(this, obj, inherits);
      let x1 = this.getLength('x1', this.getVWidth(), 0),
          y1 = this.getLength('y1', this.getVHeight(), 0),
          x2 = this.getLength('x2', this.getVWidth(), 0),
          y2 = this.getLength('y2', this.getVHeight(), 0);
      this.shape = new SvgShape().M(x1, y1).L(x2, y2);
    };

    var SvgElemPolyline = function(obj, inherits) {
      SvgElemBasicShape.call(this, obj, inherits);
      let points = this.getNumberList('points');
      this.shape = new SvgShape();
      for (let i = 0; i < points.length - 1; i += 2) {
        if (i === 0) {
          this.shape.M(points[i], points[i+1]);
        } else {
          this.shape.L(points[i], points[i+1]);
        }
      }
      if (points.error) {warningCallback('SvgElemPolygon: unexpected string ' + points.error);}
      if (points.length % 2 === 1) {warningCallback('SvgElemPolyline: uneven number of coordinates');}
    };

    var SvgElemPolygon = function(obj, inherits) {
      SvgElemBasicShape.call(this, obj, inherits);
      let points = this.getNumberList('points');
      this.shape = new SvgShape();
      for (let i = 0; i < points.length - 1; i += 2) {
        if (i === 0) {
          this.shape.M(points[i], points[i+1]);
        } else {
          this.shape.L(points[i], points[i+1]);
        }
      }
      this.shape.Z();
      if (points.error) {warningCallback('SvgElemPolygon: unexpected string ' + points.error);}
      if (points.length % 2 === 1) {warningCallback('SvgElemPolygon: uneven number of coordinates');}
    };

    var SvgElemPath = function(obj, inherits) {
      SvgElemBasicShape.call(this, obj, inherits);
      this.shape = new SvgShape().path(this.attr('d'));
      let pathLength = this.getLength('pathLength', this.getViewport());
      this.pathLength = pathLength > 0 ? pathLength : undefined;
      this.dashScale = (this.pathLength !== undefined ? this.shape.totalLength / this.pathLength : 1);
    };

    var SvgElemMarker = function(obj, inherits) {
      SvgElemHasChildren.call(this, obj, inherits);
      let width = this.getLength('markerWidth', this.getParentVWidth(), 3),
          height = this.getLength('markerHeight', this.getParentVHeight(), 3),
          viewBox = this.getViewbox('viewBox', [0, 0, width, height]);
      this.getVWidth = function() {
        return viewBox[2];
      };
      this.getVHeight = function() {
        return viewBox[3];
      };
      this.drawMarker = function(isClip, isMask, posArray, strokeWidth) {
        doc.save();
        let orient = this.attr('orient'),
            units = this.attr('markerUnits'),
            rotate = (orient === 'auto' ? posArray[2] : (parseFloat(orient) || 0) * Math.PI / 180),
            scale = (units === 'userSpaceOnUse' ? 1 : strokeWidth);
        doc.transform(Math.cos(rotate) * scale, Math.sin(rotate) * scale, -Math.sin(rotate) * scale, Math.cos(rotate) * scale, posArray[0], posArray[1]);
        let refX = this.getLength('refX', this.getVWidth(), 0),
            refY = this.getLength('refY', this.getVHeight(), 0),
            aspectRatioMatrix = parseAspectRatio(this.attr('preserveAspectRatio'), width, height, viewBox[2], viewBox[3], 0.5);
        if (this.get('overflow') === 'hidden') {
          doc.rect(aspectRatioMatrix[0] * (viewBox[0] + viewBox[2] / 2 - refX) - width / 2, aspectRatioMatrix[3] * (viewBox[1] + viewBox[3] / 2 - refY) - height / 2, width, height).clip();
        }
        doc.transform.apply(doc, aspectRatioMatrix);
        doc.translate(-refX, -refY);
        let group;
        if (this.get('opacity') < 1 && !isClip) {
          group = docBeginGroup(getPageBBox());
        }
        this.drawChildren(isClip, isMask);
        if (group) {
          docEndGroup(group);
          doc.fillOpacity(this.get('opacity'));
          docInsertGroup(group);
        }
        doc.restore();
      };
    };

    var SvgElemClipPath = function(obj, inherits) {
      SvgElemHasChildren.call(this, obj, inherits);
      this.useMask = function(bBox) {
        let group = docBeginGroup(getPageBBox());
        doc.save();
        doc.transform.apply(doc, this.get('transform'));
        if (this.attr('clipPathUnits') === 'objectBoundingBox') {
          doc.transform(bBox[2] - bBox[0], 0, 0, bBox[3] - bBox[1], bBox[0], bBox[1]);
        }
        this.clip();
        this.drawChildren(true, false);
        doc.restore();
        docEndGroup(group);
        docApplyMask(group, true);
      };
    };

    var SvgElemMask = function(obj, inherits) {
      SvgElemHasChildren.call(this, obj, inherits);
      this.useMask = function(bBox) {
        let group = docBeginGroup(getPageBBox());
        doc.save();
        let x, y, w, h;
        if (this.attr('maskUnits') === 'userSpaceOnUse') {
          x = this.getLength('x', this.getVWidth(), -0.1 * (bBox[2] - bBox[0]) + bBox[0]);
          y = this.getLength('y', this.getVHeight(), -0.1 * (bBox[3] - bBox[1]) + bBox[1]);
          w = this.getLength('width', this.getVWidth(), 1.2 * (bBox[2] - bBox[0]));
          h = this.getLength('height', this.getVHeight(), 1.2 * (bBox[3] - bBox[1]));
        } else {
          x = this.getLength('x', this.getVWidth(), -0.1) * (bBox[2] - bBox[0]) + bBox[0];
          y = this.getLength('y', this.getVHeight(), -0.1) * (bBox[3] - bBox[1]) + bBox[1];
          w = this.getLength('width', this.getVWidth(), 1.2) * (bBox[2] - bBox[0]);
          h = this.getLength('height', this.getVHeight(), 1.2) * (bBox[3] - bBox[1]);
        }
        if (this.attr('maskContentUnits') === 'objectBoundingBox') {
          doc.transform(bBox[2] - bBox[0], 0, 0, bBox[3] - bBox[1], bBox[0], bBox[1]);
        }
        this.clip();
        this.drawChildren(false, true);
        doc.restore();
        docEndGroup(group);
        docApplyMask(group, true);
      };
    };

    var SvgElemTextContainer = function(obj, inherits) {
      SvgElemStylable.call(this, obj, inherits);
      this.allowedChildren = ['tspan', '#text', '#cdata-section', 'a'];
      this.isText = true;
      this.getBoundingShape = function() {
        let shape = new SvgShape();
        for (let i = 0; i < this._pos.length; i++) {
          let pos = this._pos[i];
          if (!pos.hidden) {
            let dx0 = pos.ascent * Math.sin(pos.rotate), dy0 = -pos.ascent * Math.cos(pos.rotate),
                dx1 = pos.descent * Math.sin(pos.rotate), dy1 = -pos.descent * Math.cos(pos.rotate),
                dx2 = pos.width * Math.cos(pos.rotate), dy2 = pos.width * Math.sin(pos.rotate);
            shape.M(pos.x + dx0, pos.y + dy0).L(pos.x + dx0 + dx2, pos.y + dy0 + dy2)
                 .M(pos.x + dx1 + dx2, pos.y + dy1 + dy2).L(pos.x + dx1, pos.y + dy1);
          }
        }
        return shape;
      };
      this.drawTextInDocument = function(isClip, isMask) {
        if (this.link && !isClip && !isMask) {this.addLink();}
        if (this.get('text-decoration') === 'underline') {
          this.decorate(0.05 * this._font.size, -0.075 * this._font.size, isClip, isMask);
        }
        if (this.get('text-decoration') === 'overline') {
          this.decorate(0.05 * this._font.size, getAscent(this._font.font, this._font.size) + 0.075 * this._font.size, isClip, isMask);
        }
        let fill = this.getFill(isClip, isMask),
            stroke = this.getStroke(isClip, isMask),
            strokeWidth = this.get('stroke-width');
        if (this._font.fauxBold) {
          if (!stroke) {
            stroke = fill;
            strokeWidth = this._font.size * 0.03;
          } else {
            strokeWidth += this._font.size * 0.03;
          }
        }
        let children = this.getChildren();
        for (let i = 0; i < children.length; i++) {
          let childElem = children[i];
          switch(childElem.name) {
            case 'tspan': case 'textPath': case 'a':
              if (childElem.get('display') !== 'none') {
                childElem.drawTextInDocument(isClip, isMask);
              }
              break;
            case '#text': case '#cdata-section':
              if (this.get('visibility') === 'hidden') {continue;}
              if (fill || stroke || isClip) {
                if (fill) {
                  docFillColor(fill);
                }
                if (stroke && strokeWidth) {
                  docStrokeColor(stroke);
                  doc.lineWidth(strokeWidth)
                     .miterLimit(this.get('stroke-miterlimit'))
                     .lineJoin(this.get('stroke-linejoin'))
                     .lineCap(this.get('stroke-linecap'));
                  docApplyDash(this.get('stroke-dasharray'), this.get('stroke-dashoffset'));
                }
                docBeginText(this._font.font, this._font.size);
                docSetTextMode(!!fill, !!stroke);
                docWriteGlyphs(childElem._pos, this._font);
                docEndText();
              }
              break;
          }
        }
        if (this.get('text-decoration') === 'line-through') {
          this.decorate(0.05 * this._font.size, 0.5 * (getAscent(this._font.font, this._font.size) + getDescent(this._font.font, this._font.size)), isClip, isMask);
        }
      };
      this.decorate = function(lineWidth, linePosition, isClip, isMask) {
        let fill = this.getFill(isClip, isMask),
            stroke = this.getStroke(isClip, isMask);
        if (fill) {
          docFillColor(fill);
        }
        if (stroke) {
          docStrokeColor(stroke);
          doc.lineWidth(this.get('stroke-width'))
             .miterLimit(this.get('stroke-miterlimit'))
             .lineJoin(this.get('stroke-linejoin'))
             .lineCap(this.get('stroke-linecap'));
          docApplyDash(this.get('stroke-dasharray'), this.get('stroke-dashoffset'));
        }
        for (let j = 0, pos = this._pos; j < pos.length; j++) {
          if (!pos[j].hidden && isNotEqual(pos[j].width, 0)) {
            let dx0 = (linePosition + lineWidth / 2) * Math.sin(pos[j].rotate),
                dy0 = -(linePosition + lineWidth / 2) * Math.cos(pos[j].rotate),
                dx1 = (linePosition - lineWidth / 2) * Math.sin(pos[j].rotate),
                dy1 = -(linePosition - lineWidth / 2) * Math.cos(pos[j].rotate),
                dx2 = pos[j].width * Math.cos(pos[j].rotate),
                dy2 = pos[j].width * Math.sin(pos[j].rotate);
            new SvgShape().M(pos[j].x + dx0, pos[j].y + dy0)
                          .L(pos[j].x + dx0 + dx2, pos[j].y + dy0 + dy2)
                          .L(pos[j].x + dx1 + dx2, pos[j].y + dy1 + dy2)
                          .L(pos[j].x + dx1, pos[j].y + dy1).Z()
                          .insertInDocument();
            if (fill && stroke) {
              doc.fillAndStroke();
            } else if (fill) {
              doc.fill();
            } else if (stroke) {
              doc.stroke();
            }
          }
        }
      };
    };

    var SvgElemTextNode = function(obj, inherits) {
      this.name = obj.nodeName;
      this.textContent = obj.nodeValue;
    };

    var SvgElemTspan = function(obj, inherits) {
      SvgElemTextContainer.call(this, obj, inherits);
    };

    var SvgElemTextPath = function(obj, inherits) {
      SvgElemTextContainer.call(this, obj, inherits);
      let pathObject, pathLength, temp;
      if ((temp = this.attr('path')) && temp.trim() !== '') {
        let pathLength = this.getLength('pathLength', this.getViewport());
        this.pathObject = new SvgShape().path(temp);
        this.pathLength = pathLength > 0 ? pathLength : this.pathObject.totalLength;
        this.pathScale = this.pathObject.totalLength / this.pathLength;
      } else if ((temp = this.getUrl('href') || this.getUrl('xlink:href')) && temp.nodeName === 'path') {
        let pathElem = new SvgElemPath(temp, this);
        this.pathObject = pathElem.shape.clone().transform(pathElem.get('transform'));
        this.pathLength = this.chooseValue(pathElem.pathLength, this.pathObject.totalLength);
        this.pathScale = this.pathObject.totalLength / this.pathLength;
      }
    };

    var SvgElemText = function(obj, inherits) {
      SvgElemTextContainer.call(this, obj, inherits);
      this.allowedChildren = ['textPath', 'tspan', '#text', '#cdata-section', 'a'];
      (function (textParentElem) {
        let processedText = '', remainingText = obj.textContent, textPaths = [], currentChunk = [], currentAnchor, currentDirection, currentX = 0, currentY = 0;
        function doAnchoring() {
          if (currentChunk.length) {
            let last = currentChunk[currentChunk.length - 1];
            let first = currentChunk[0]
            let width = last.x + last.width - first.x;
            let anchordx = {'startltr': 0, 'middleltr': 0.5, 'endltr': 1, 'startrtl': 1, 'middlertl': 0.5, 'endrtl': 0}[currentAnchor + currentDirection] * width || 0;
            for (let i = 0; i < currentChunk.length; i++) {
              currentChunk[i].x -= anchordx;
            }
          }
          currentChunk = [];
        }
        function adjustLength(pos, length, spacingAndGlyphs) {
          let firstChar = pos[0], lastChar = pos[pos.length - 1],
              startX = firstChar.x, endX = lastChar.x + lastChar.width;
          if (spacingAndGlyphs) {
            let textScale = length / (endX - startX);
            if (textScale > 0 && textScale < Infinity) {
              for (let j = 0; j < pos.length; j++) {
                pos[j].continuous = false;
                pos[j].x = startX + textScale * (pos[j].x - startX);
                pos[j].scale *= textScale;
                pos[j].width *= textScale;
              }
            }
          } else {
            if (pos.length >= 2) {
              let spaceDiff = (length - (endX - startX)) / (pos.length - 1);
              for (let j = 0; j < pos.length; j++) {
                pos[j].continuous = false;
                pos[j].x += j * spaceDiff;
              }
            }
          }
          currentX += length - (endX - startX);
        }
        function recursive(currentElem, parentElem) {
          currentElem._x = combineArrays(currentElem.getLengthList('x', currentElem.getVWidth()), (parentElem ? parentElem._x.slice(parentElem._pos.length) : []));
          currentElem._y = combineArrays(currentElem.getLengthList('y', currentElem.getVHeight()), (parentElem ? parentElem._y.slice(parentElem._pos.length) : []));
          currentElem._dx = combineArrays(currentElem.getLengthList('dx', currentElem.getVWidth()), (parentElem ? parentElem._dx.slice(parentElem._pos.length) : []));
          currentElem._dy = combineArrays(currentElem.getLengthList('dy', currentElem.getVHeight()), (parentElem ? parentElem._dy.slice(parentElem._pos.length) : []));
          currentElem._rot = combineArrays(currentElem.getNumberList('rotate'), (parentElem ? parentElem._rot.slice(parentElem._pos.length) : []));
          currentElem._defRot = currentElem.chooseValue(currentElem._rot[currentElem._rot.length - 1], parentElem && parentElem._defRot, 0);
          if (currentElem.name === 'textPath') {currentElem._y = [];}
          let fontOptions = {fauxItalic: false, fauxBold: false},
              fontNameorLink = fontCallback(currentElem.get('font-family'), currentElem.get('font-weight') === 'bold', currentElem.get('font-style') === 'italic', fontOptions);
          try {
            doc.font(fontNameorLink);
          } catch(e) {
            warningCallback('SVGElemText: failed to open font "' + fontNameorLink + '" in PDFKit: ' + e.message);
          }
          currentElem._pos = [];
          currentElem._index = 0;
          currentElem._font = {font: doc._font, size: currentElem.get('font-size'), fauxItalic: fontOptions.fauxItalic, fauxBold: fontOptions.fauxBold};
          let textLength = currentElem.getLength('textLength', currentElem.getVWidth(), undefined),
              spacingAndGlyphs = currentElem.attr('lengthAdjust') === 'spacingAndGlyphs',
              wordSpacing = currentElem.get('word-spacing'),
              letterSpacing = currentElem.get('letter-spacing'),
              textAnchor = currentElem.get('text-anchor'),
              textDirection = currentElem.get('direction'),
              // `alignment-baseline` and `baseline-shift` have no effect on
              // `<text>` elements according to the SVG spec. So, detect when
              // we're styling a `<text>` element and ignore
              // `alignment-baseline` (only factoring in `dominant-baseline`)
              // and `baseline-shift` (which can only have the default value of
              // `baseline`).
              //
              // Note that Chrome (as of v99) incorrectly factors in
              // `alignment-baseline` on `<text>` elements, while Firefox
              // correctly follows the spec and ignores it. This means that our
              // output will differ from Chrome's in these cases, but conform to
              // SVG specification.
              isTextElem = currentElem.name === 'text',
              baselineAttr = isTextElem ? currentElem.get('dominant-baseline') : (currentElem.get('alignment-baseline') || currentElem.get('dominant-baseline')),
              baselineShiftAttr = isTextElem ? 'baseline' : currentElem.get('baseline-shift'),
              baseline = getBaseline(currentElem._font.font, currentElem._font.size, baselineAttr, baselineShiftAttr);
          if (currentElem.name === 'textPath') {
            doAnchoring();
            currentX = currentY = 0;
          }
          let children = currentElem.getChildren();
          for (let i = 0; i < children.length; i++) {
            let childElem = children[i];
            switch(childElem.name) {
              case 'tspan': case 'textPath': case 'a':
                recursive(childElem, currentElem);
                break;
              case '#text': case '#cdata-section':
                let rawText = childElem.textContent, renderedText = rawText, words;
                childElem._font = currentElem._font;
                childElem._pos = [];
                remainingText = remainingText.substring(rawText.length);
                if (currentElem.get('xml:space') === 'preserve') {
                  renderedText = renderedText.replace(/[\s]/g, ' ');
                } else {
                  renderedText = renderedText.replace(/[\s]+/g, ' ');
                  if (processedText.match(/[\s]$|^$/)) {renderedText = renderedText.replace(/^[\s]/, '');}
                  if (remainingText.match(/^[\s]*$/)) {renderedText = renderedText.replace(/[\s]$/, '');}
                }
                processedText += rawText;
                if (wordSpacing === 0) {
                  words = [renderedText];
                } else {
                  words = renderedText.split(/(\s)/);
                }
                for (let w = 0; w < words.length; w++) {
                  let pos = getTextPos(currentElem._font.font, currentElem._font.size, words[w]);
                  for (let j = 0; j < pos.length; j++) {
                    let index = currentElem._index,
                        xAttr = currentElem._x[index],
                        yAttr = currentElem._y[index],
                        dxAttr = currentElem._dx[index],
                        dyAttr = currentElem._dy[index],
                        rotAttr = currentElem._rot[index],
                        continuous = !(w === 0 && j === 0);
                    if (letterSpacing !== 0) {continuous = false}
                    if (wordSpacing !== 0) {continuous = false}
                    if (xAttr !== undefined) {continuous = false; doAnchoring(); currentX = xAttr;}
                    if (yAttr !== undefined) {continuous = false; doAnchoring(); currentY = yAttr;}
                    if (dxAttr !== undefined) {continuous = false; currentX += dxAttr;}
                    if (dyAttr !== undefined) {continuous = false; currentY += dyAttr;}
                    if (rotAttr !== undefined || currentElem._defRot !== 0) {continuous = false;}
                    let position = {
                      glyph: pos[j].glyph,
                      rotate: (Math.PI / 180) * currentElem.chooseValue(rotAttr, currentElem._defRot),
                      x: currentX + pos[j].xOffset,
                      kern: pos[j].kern,
                      y: currentY + baseline + pos[j].yOffset,
                      width: pos[j].width,
                      ascent: getAscent(currentElem._font.font, currentElem._font.size),
                      descent: getDescent(currentElem._font.font, currentElem._font.size),
                      scale: 1,
                      hidden: false,
                      continuous: continuous
                    };
                    currentChunk.push(position);
                    childElem._pos.push(position);
                    currentElem._pos.push(position);
                    currentElem._index += pos[j].unicode.length;
                    if (currentChunk.length === 1) {
                      currentAnchor = textAnchor;
                      currentDirection = textDirection;
                    }
                    currentX += pos[j].xAdvance + letterSpacing;
                    currentY += pos[j].yAdvance;
                  }
                  if (words[w] === ' ') {
                    currentX += wordSpacing;
                  }
                }
                break;
              default:
                remainingText = remainingText.substring(childElem.textContent.length);
            }
          }
          if (textLength && currentElem._pos.length) {
            adjustLength(currentElem._pos, textLength, spacingAndGlyphs);
          }
          if (currentElem.name === 'textPath' || currentElem.name === 'text') {
            doAnchoring();
          }
          if (currentElem.name === 'textPath') {
            textPaths.push(currentElem);
            let pathObject = currentElem.pathObject;
            if (pathObject) {
              currentX = pathObject.endPoint[0]; currentY = pathObject.endPoint[1];
            }
          }
          if (parentElem) {
            parentElem._pos = parentElem._pos.concat(currentElem._pos);
            parentElem._index += currentElem._index;
          }
        }
        function textOnPath(currentElem) {
          let pathObject = currentElem.pathObject,
              pathLength = currentElem.pathLength,
              pathScale = currentElem.pathScale;
          if (pathObject) {
            let textOffset = currentElem.getLength('startOffset', pathLength, 0);
            for (let j = 0; j < currentElem._pos.length; j++) {
              let charMidX = textOffset + currentElem._pos[j].x + 0.5 * currentElem._pos[j].width;
              if (charMidX > pathLength || charMidX < 0) {
                currentElem._pos[j].hidden = true;
              } else {
                let pointOnPath = pathObject.getPointAtLength(charMidX * pathScale);
                if (isNotEqual(pathScale, 1)) {
                  currentElem._pos[j].scale *= pathScale;
                  currentElem._pos[j].width *= pathScale;
                }
                currentElem._pos[j].x = pointOnPath[0] - 0.5 * currentElem._pos[j].width * Math.cos(pointOnPath[2]) - currentElem._pos[j].y * Math.sin(pointOnPath[2]);
                currentElem._pos[j].y = pointOnPath[1] - 0.5 * currentElem._pos[j].width * Math.sin(pointOnPath[2]) + currentElem._pos[j].y * Math.cos(pointOnPath[2]);
                currentElem._pos[j].rotate = pointOnPath[2] + currentElem._pos[j].rotate;
                currentElem._pos[j].continuous = false;
              }
            }
          } else {
            for (let j = 0; j < currentElem._pos.length; j++) {
              currentElem._pos[j].hidden = true;
            }
          }
        }
        recursive(textParentElem, null);
        for (let i = 0; i < textPaths.length; i++) {
          textOnPath(textPaths[i]);
        }
      })(this);
      this.getTransformation = function() {
        return this.get('transform');
      };
      this.drawInDocument = function(isClip, isMask) {
        doc.save();
        this.transform();
        this.clip();
        let masked = this.mask(), group;
        if (masked) {
          group = docBeginGroup(getPageBBox());
        }
        this.drawTextInDocument(isClip, isMask);
        if (group) {
          docEndGroup(group);
          docInsertGroup(group);
        }
        doc.restore();
      };
    };

    options = options || {};
    var pxToPt = options.assumePt ? 1 : (72/96), // 1px = 72/96pt, but only if assumePt is false
        viewportWidth = (options.width || doc.page.width) / pxToPt,
        viewportHeight = (options.height || doc.page.height) / pxToPt,
        preserveAspectRatio = options.preserveAspectRatio || null, // default to null so that the attr can override if not passed
        useCSS = options.useCSS && typeof SVGElement !== 'undefined' && svg instanceof SVGElement && typeof getComputedStyle === 'function',
        warningCallback = options.warningCallback,
        fontCallback = options.fontCallback,
        imageCallback = options.imageCallback,
        colorCallback = options.colorCallback,
        documentCallback = options.documentCallback,
        precision = Math.ceil(Math.max(1, options.precision)) || 3,
        groupStack = [],
        documentCache = {},
        links = [],
        styleRules = [];

    if (typeof warningCallback !== 'function') {
      warningCallback = function(str) {
        if (typeof console !== undefined && typeof console.warn === 'function') {console.warn(str);}
      };
    }
    if (typeof fontCallback !== 'function') {
      fontCallback = function(family, bold, italic, fontOptions) {
        // Check if the font is already registered in the document
        if (bold && italic) {
          if (doc._registeredFonts.hasOwnProperty(family + '-BoldItalic')) {
            return family + '-BoldItalic';
          } else if (doc._registeredFonts.hasOwnProperty(family + '-Italic')) {
            fontOptions.fauxBold = true;
            return family + '-Italic';
          } else if (doc._registeredFonts.hasOwnProperty(family + '-Bold')) {
            fontOptions.fauxItalic = true;
            return family + '-Bold';
          } else if (doc._registeredFonts.hasOwnProperty(family)) {
            fontOptions.fauxBold = true;
            fontOptions.fauxItalic = true;
            return family;
          }
        }
        if (bold && !italic) {
          if (doc._registeredFonts.hasOwnProperty(family + '-Bold')) {
            return family + '-Bold';
          } else if (doc._registeredFonts.hasOwnProperty(family)) {
            fontOptions.fauxBold = true;
            return family;
          }
        }
        if (!bold && italic) {
          if (doc._registeredFonts.hasOwnProperty(family + '-Italic')) {
            return family + '-Italic';
          } else if (doc._registeredFonts.hasOwnProperty(family)) {
            fontOptions.fauxItalic = true;
            return family;
          }
        }
        if (!bold && !italic) {
          if (doc._registeredFonts.hasOwnProperty(family)) {
            return family;
          }
        }
        // Use standard fonts as fallback
        if (family.match(/(?:^|,)\s*serif\s*$/)) {
          if (bold && italic) {return 'Times-BoldItalic';}
          if (bold && !italic) {return 'Times-Bold';}
          if (!bold && italic) {return 'Times-Italic';}
          if (!bold && !italic) {return 'Times-Roman';}
        } else if (family.match(/(?:^|,)\s*monospace\s*$/)) {
          if (bold && italic) {return 'Courier-BoldOblique';}
          if (bold && !italic) {return 'Courier-Bold';}
          if (!bold && italic) {return 'Courier-Oblique';}
          if (!bold && !italic) {return 'Courier';}
        } else if (family.match(/(?:^|,)\s*sans-serif\s*$/) || true) {
          if (bold && italic) {return 'Helvetica-BoldOblique';}
          if (bold && !italic) {return 'Helvetica-Bold';}
          if (!bold && italic) {return 'Helvetica-Oblique';}
          if (!bold && !italic) {return 'Helvetica';}
        }
      };
    }
    if (typeof imageCallback !== 'function') {
      imageCallback = function(link) {
        return link.replace(/\s+/g, '');
      };
    }
    if (typeof colorCallback !== 'function') {
      colorCallback = null;
    } else {
      for (let color in DefaultColors) {
        let newColor = colorCallback(DefaultColors[color]);
        DefaultColors[color][0] = newColor[0];
        DefaultColors[color][1] = newColor[1];
      }
    }
    if (typeof documentCallback !== 'function') {
      documentCallback = null;
    }

    if (typeof svg === 'string') {svg = parseXml(svg);}
    if (svg) {
      let styles = svg.getElementsByTagName('style');
      for (let i = 0; i < styles.length; i++) {
        styleRules = styleRules.concat(parseStyleSheet(styles[i].textContent));
      }
      let elem = createSVGElement(svg, null);
      if (typeof elem.drawInDocument === 'function') {
        if (options.useCSS && !useCSS) {
          warningCallback('SVGtoPDF: useCSS option can only be used for SVG *elements* in compatible browsers');
        }
        let savedFillColor = doc._fillColor;
        doc.save().translate(x || 0, y || 0).scale(pxToPt);
        elem.drawInDocument();
        for (let i = 0; i < links.length; i++) {
          doc.page.annotations.push(links[i]);
        }
        doc.restore();
        doc._fillColor = savedFillColor;
      } else {
        warningCallback('SVGtoPDF: this element can\'t be rendered directly: ' + svg.nodeName);
      }
    } else {
      warningCallback('SVGtoPDF: the input does not look like a valid SVG');
    }

};

if (typeof module !== 'undefined' && module && typeof module.exports !== 'undefined') {
  module.exports = SVGtoPDF;
}
