/* globals jsPDF */ 
 | 
/* 
 | 
 * svgToPdf.js 
 | 
 * 
 | 
 * Copyright 2012-2014 Florian Hülsmann <fh@cbix.de> 
 | 
 * Copyright 2014 Ben Gribaudo <www.bengribaudo.com> 
 | 
 * 
 | 
 * This script is free software: you can redistribute it and/or modify 
 | 
 * it under the terms of the GNU Lesser General Public License as published by 
 | 
 * the Free Software Foundation, either version 3 of the License, or 
 | 
 * (at your option) any later version. 
 | 
 * 
 | 
 * This script is distributed in the hope that it will be useful, 
 | 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 | 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 | 
 * GNU Lesser General Public License for more details. 
 | 
 * 
 | 
 * You should have received a copy of the GNU Lesser General Public License 
 | 
 * along with this file.  If not, see <http://www.gnu.org/licenses/>. 
 | 
 * 
 | 
 */ 
 | 
  
 | 
import RGBColor from '../canvg/rgbcolor.js'; 
 | 
  
 | 
const jsPDFAPI = jsPDF.API; 
 | 
const pdfSvgAttr = { 
 | 
  // allowed attributes. all others are removed from the preview. 
 | 
  g: ['stroke', 'fill', 'stroke-width'], 
 | 
  line: ['x1', 'y1', 'x2', 'y2', 'stroke', 'stroke-width'], 
 | 
  rect: ['x', 'y', 'width', 'height', 'stroke', 'fill', 'stroke-width'], 
 | 
  ellipse: ['cx', 'cy', 'rx', 'ry', 'stroke', 'fill', 'stroke-width'], 
 | 
  circle: ['cx', 'cy', 'r', 'stroke', 'fill', 'stroke-width'], 
 | 
  polygon: ['points', 'stroke', 'fill', 'stroke-width'], 
 | 
  // polyline attributes are the same as those of polygon 
 | 
  text: ['x', 'y', 'font-size', 'font-family', 'text-anchor', 'font-weight', 'font-style', 'fill'] 
 | 
}; 
 | 
  
 | 
const attributeIsNotEmpty = function (node, attr) { 
 | 
  const attVal = attr ? node.getAttribute(attr) : node; 
 | 
  return attVal !== '' && attVal !== null; 
 | 
}; 
 | 
  
 | 
const nodeIs = function (node, possible) { 
 | 
  return possible.includes(node.tagName.toLowerCase()); 
 | 
}; 
 | 
  
 | 
const removeAttributes = function (node, attributes) { 
 | 
  const toRemove = []; 
 | 
  [].forEach.call(node.attributes, function (a) { 
 | 
    if (attributeIsNotEmpty(a) && !attributes.includes(a.name.toLowerCase())) { 
 | 
      toRemove.push(a.name); 
 | 
    } 
 | 
  }); 
 | 
  
 | 
  toRemove.forEach((a) => { 
 | 
    node.removeAttribute(a.name); 
 | 
  }); 
 | 
}; 
 | 
  
 | 
const numRgx = /[+-]?(?:\d+\.\d*|\d+|\.\d+)(?:[eE][+-]?\d+)?/g; 
 | 
const getLinesOptionsOfPoly = function (node) { 
 | 
  let nums = node.getAttribute('points'); 
 | 
  nums = (nums && nums.match(numRgx)) || []; 
 | 
  if (nums && nums.length) { 
 | 
    nums = nums.map(Number); 
 | 
    if (nums.length % 2) { 
 | 
      nums.length--; 
 | 
    } 
 | 
  } 
 | 
  if (nums.length < 4) { 
 | 
    console.log('invalid points attribute:', node); 
 | 
    return; 
 | 
  } 
 | 
  const [x, y] = nums, lines = []; 
 | 
  for (let i = 2; i < nums.length; i += 2) { 
 | 
    lines.push([nums[i] - nums[i - 2], nums[i + 1] - nums[i - 1]]); 
 | 
  } 
 | 
  return {x, y, lines}; 
 | 
}; 
 | 
  
 | 
const svgElementToPdf = function (element, pdf, options) { 
 | 
  // pdf is a jsPDF object 
 | 
  // console.log('options =', options); 
 | 
  const remove = (options.removeInvalid === undefined ? false : options.removeInvalid); 
 | 
  const k = (options.scale === undefined ? 1.0 : options.scale); 
 | 
  let colorMode = null; 
 | 
  [].forEach.call(element.children, function (node) { 
 | 
    // console.log('passing: ', node); 
 | 
    // let hasStrokeColor = false; 
 | 
    let hasFillColor = false; 
 | 
    let fillRGB; 
 | 
    if (nodeIs(node, ['g', 'line', 'rect', 'ellipse', 'circle', 'polygon', 'polyline', 'text'])) { 
 | 
      const fillColor = node.getAttribute('fill'); 
 | 
      if (attributeIsNotEmpty(fillColor)) { 
 | 
        fillRGB = new RGBColor(fillColor); 
 | 
        if (fillRGB.ok) { 
 | 
          hasFillColor = true; 
 | 
          colorMode = 'F'; 
 | 
        } else { 
 | 
          colorMode = null; 
 | 
        } 
 | 
      } 
 | 
    } 
 | 
    if (nodeIs(node, ['g', 'line', 'rect', 'ellipse', 'circle', 'polygon', 'polyline'])) { 
 | 
      if (hasFillColor) { 
 | 
        pdf.setFillColor(fillRGB.r, fillRGB.g, fillRGB.b); 
 | 
      } 
 | 
      if (attributeIsNotEmpty(node, 'stroke-width')) { 
 | 
        pdf.setLineWidth(k * parseInt(node.getAttribute('stroke-width'), 10)); 
 | 
      } 
 | 
      const strokeColor = node.getAttribute('stroke'); 
 | 
      if (attributeIsNotEmpty(strokeColor)) { 
 | 
        const strokeRGB = new RGBColor(strokeColor); 
 | 
        if (strokeRGB.ok) { 
 | 
          // hasStrokeColor = true; 
 | 
          pdf.setDrawColor(strokeRGB.r, strokeRGB.g, strokeRGB.b); 
 | 
          if (colorMode === 'F') { 
 | 
            colorMode = 'FD'; 
 | 
          } else { 
 | 
            colorMode = 'S'; 
 | 
          } 
 | 
        } else { 
 | 
          colorMode = null; 
 | 
        } 
 | 
      } 
 | 
    } 
 | 
    const tag = node.tagName.toLowerCase(); 
 | 
    switch (tag) { 
 | 
    case 'svg': 
 | 
    case 'a': 
 | 
    case 'g': 
 | 
      svgElementToPdf(node, pdf, options); 
 | 
      removeAttributes(node, pdfSvgAttr.g); 
 | 
      break; 
 | 
    case 'line': 
 | 
      pdf.line( 
 | 
        k * parseInt(node.getAttribute('x1'), 10), 
 | 
        k * parseInt(node.getAttribute('y1'), 10), 
 | 
        k * parseInt(node.getAttribute('x2'), 10), 
 | 
        k * parseInt(node.getAttribute('y2'), 10) 
 | 
      ); 
 | 
      removeAttributes(node, pdfSvgAttr.line); 
 | 
      break; 
 | 
    case 'rect': 
 | 
      pdf.rect( 
 | 
        k * parseInt(node.getAttribute('x'), 10), 
 | 
        k * parseInt(node.getAttribute('y'), 10), 
 | 
        k * parseInt(node.getAttribute('width'), 10), 
 | 
        k * parseInt(node.getAttribute('height'), 10), 
 | 
        colorMode 
 | 
      ); 
 | 
      removeAttributes(node, pdfSvgAttr.rect); 
 | 
      break; 
 | 
    case 'ellipse': 
 | 
      pdf.ellipse( 
 | 
        k * parseInt(node.getAttribute('cx'), 10), 
 | 
        k * parseInt(node.getAttribute('cy'), 10), 
 | 
        k * parseInt(node.getAttribute('rx'), 10), 
 | 
        k * parseInt(node.getAttribute('ry'), 10), 
 | 
        colorMode 
 | 
      ); 
 | 
      removeAttributes(node, pdfSvgAttr.ellipse); 
 | 
      break; 
 | 
    case 'circle': 
 | 
      pdf.circle( 
 | 
        k * parseInt(node.getAttribute('cx'), 10), 
 | 
        k * parseInt(node.getAttribute('cy'), 10), 
 | 
        k * parseInt(node.getAttribute('r'), 10), 
 | 
        colorMode 
 | 
      ); 
 | 
      removeAttributes(node, pdfSvgAttr.circle); 
 | 
      break; 
 | 
    case 'polygon': 
 | 
    case 'polyline': 
 | 
      const linesOptions = getLinesOptionsOfPoly(node); 
 | 
      if (linesOptions) { 
 | 
        pdf.lines( 
 | 
          linesOptions.lines, 
 | 
          k * linesOptions.x, 
 | 
          k * linesOptions.y, 
 | 
          [k, k], 
 | 
          colorMode, 
 | 
          tag === 'polygon' // polygon is closed, polyline is not closed 
 | 
        ); 
 | 
      } 
 | 
      removeAttributes(node, pdfSvgAttr.polygon); 
 | 
      break; 
 | 
    // TODO: path 
 | 
    case 'text': 
 | 
      if (node.hasAttribute('font-family')) { 
 | 
        switch ((node.getAttribute('font-family') || '').toLowerCase()) { 
 | 
        case 'serif': pdf.setFont('times'); break; 
 | 
        case 'monospace': pdf.setFont('courier'); break; 
 | 
        default: 
 | 
          node.setAttribute('font-family', 'sans-serif'); 
 | 
          pdf.setFont('helvetica'); 
 | 
        } 
 | 
      } 
 | 
      if (hasFillColor) { 
 | 
        pdf.setTextColor(fillRGB.r, fillRGB.g, fillRGB.b); 
 | 
      } 
 | 
      let fontType = ''; 
 | 
      if (node.hasAttribute('font-weight')) { 
 | 
        if (node.getAttribute('font-weight') === 'bold') { 
 | 
          fontType = 'bold'; 
 | 
        } else { 
 | 
          node.removeAttribute('font-weight'); 
 | 
        } 
 | 
      } 
 | 
      if (node.hasAttribute('font-style')) { 
 | 
        if (node.getAttribute('font-style') === 'italic') { 
 | 
          fontType += 'italic'; 
 | 
        } else { 
 | 
          node.removeAttribute('font-style'); 
 | 
        } 
 | 
      } 
 | 
      pdf.setFontType(fontType); 
 | 
      const pdfFontSize = node.hasAttribute('font-size') 
 | 
        ? parseInt(node.getAttribute('font-size'), 10) 
 | 
        : 16; 
 | 
  
 | 
      const getWidth = (node) => { 
 | 
        let box; 
 | 
        try { 
 | 
          box = node.getBBox(); // Firefox on MacOS will raise error here 
 | 
        } catch (err) { 
 | 
          // copy and append to body so that getBBox is available 
 | 
          const nodeCopy = node.cloneNode(true); 
 | 
          const svg = node.ownerSVGElement.cloneNode(false); 
 | 
          svg.appendChild(nodeCopy); 
 | 
          document.body.appendChild(svg); 
 | 
          try { 
 | 
            box = nodeCopy.getBBox(); 
 | 
          } catch (err) { 
 | 
            box = {width: 0}; 
 | 
          } 
 | 
          document.body.removeChild(svg); 
 | 
        } 
 | 
        return box.width; 
 | 
      }; 
 | 
      // FIXME: use more accurate positioning!! 
 | 
      let x, y, xOffset = 0; 
 | 
      if (node.hasAttribute('text-anchor')) { 
 | 
        switch (node.getAttribute('text-anchor')) { 
 | 
        case 'end': xOffset = getWidth(node); break; 
 | 
        case 'middle': xOffset = getWidth(node) / 2; break; 
 | 
        case 'start': break; 
 | 
        case 'default': node.setAttribute('text-anchor', 'start'); break; 
 | 
        } 
 | 
        x = parseInt(node.getAttribute('x'), 10) - xOffset; 
 | 
        y = parseInt(node.getAttribute('y'), 10); 
 | 
      } 
 | 
      // console.log('fontSize:', pdfFontSize, 'text:', node.textContent); 
 | 
      pdf.setFontSize(pdfFontSize).text( 
 | 
        k * x, 
 | 
        k * y, 
 | 
        node.textContent 
 | 
      ); 
 | 
      removeAttributes(node, pdfSvgAttr.text); 
 | 
      break; 
 | 
    // TODO: image 
 | 
    default: 
 | 
      if (remove) { 
 | 
        console.log("can't translate to pdf:", node); 
 | 
        node.remove(); 
 | 
      } 
 | 
    } 
 | 
  }); 
 | 
  return pdf; 
 | 
}; 
 | 
  
 | 
jsPDFAPI.addSVG = function (element, x, y, options) { 
 | 
  options = (options === undefined ? {} : options); 
 | 
  options.x_offset = x; 
 | 
  options.y_offset = y; 
 | 
  
 | 
  if (typeof element === 'string') { 
 | 
    element = new DOMParser().parseFromString(element, 'text/xml').documentElement; 
 | 
  } 
 | 
  svgElementToPdf(element, this, options); 
 | 
  return this; 
 | 
}; 
 |