BenJeau / react-native-draw

SVG based data-driven React Native drawing component 🎨
https://www.npmjs.com/package/@benjeau/react-native-draw
MIT License
127 stars 42 forks source link

Is it possible to save the drawing? #26

Closed MohammedAljahdali closed 2 years ago

MohammedAljahdali commented 3 years ago

Is there a way to save what is drawn as a blob, the same you could do with HTML canvas?

BenJeau commented 3 years ago

No, but you can get the raw data from the drawing using a ref to the component and calling the getPaths functions which would give you an array of paths, https://github.com/BenJeau/react-native-draw/blob/master/src/types.ts#L3, which you can reconstruct the SVG drawing.

MohammedAljahdali commented 3 years ago

Thanks 🙏🏻

persunde commented 3 years ago

I am not familiar with the SVG specifications. Is there anyone who have a function that solves this problem? I would love to have a function that transforms the drawing from this library to a SVG file/string.

Pakile commented 2 years ago

@persunde do you have any solutions for that?

BenJeau commented 2 years ago

Sorry @persunde didn't see you comment earlier. I intend to revamp this lib to make it more dev-friendly and more customizable soon-ish (not sure when because of school and work), but @Pakile to reconstruct the drawing the getPaths function or the onPathsChange prop should help you.

with onPathsChange:

import React, { useEffect } from 'react';
import { Draw, PathType } from '@benjeau/react-native-draw';

const HEIGHT = 450;
const WIDTH = 350;

const Drawing: React.FC = () => {
  const [paths, setPaths] = useState<PathType[]>([]);
  const [svg, setSvg] = useState('');

  useEffect(() => {
    const svgPaths = paths.map(
      ({ color, path, thickness, opacity }) =>
        `<path d="${path}" stroke="${color}" stroke-width="${thickness}" fill-opacity="${opacity}"/>`,
    );

    setSvg(`
      <svg xmlns="http://www.w3.org/2000/svg" width="${WIDTH}" height="${HEIGHT}" viewBox="0 0 ${WIDTH} ${HEIGHT}">${svgPaths}</svg>
    `);
  }, [paths]);

  return <Draw height={HEIGHT} width={WIDTH} onPathsChange={setPaths} />;
};

I haven't tested this, since I don't have time unfortunately, but it should work. Please let me know if it doesn't or if there are strange behaviors.

Pakile commented 2 years ago

thanks @BenJeau for this, and can you help me a little bit about converting the svg file to point so that others can convert from the svg file and fix what was drawn

BenJeau commented 2 years ago

@Pakile I would strongly recommend simply passing along the paths with the SVG for simplicity. Here's how you can do what you want (note: this is a very optimistic and naive function, it can very easily throw errors if something is malformatted or if something escapes the regex):

Note: this would actually not work with this component since the value of d is not a straight point for point correlation. You would need to fix that part (which would be related to the dRegex to extract the information from the value of d and the logic used in the reduce function)

const svgRegex = /<svg [^<]*\>/g;
const pathRegex = /<path [^<]*\/>/g;
const propertyRegex = /([A-Ba-z]|-)+="([^"])*"/g;
const dRegex = /[^LM\s,]*/g;

const extractPropertyValues = (property) => {
  const values = property.split(`="`);

  return [values[0], values[1].substring(0, values[1].length - 1)];
};

const helperFindProperty = (propertiesValues, key) =>
  propertiesValues.find((i) => i[0] === key)[1];

const convertSvgToPath = (svgContent) => {
  const svgTag = svgContent.match(svgRegex);
  const svgProperties = svgTag[0].match(propertyRegex);
  const svgPropertiesValues = svgProperties.map(extractPropertyValues);

  const pathTags = svgContent.match(pathRegex);
  const pathProperties = pathTags.map((i) => i.match(propertyRegex));
  const pathPropertiesValues = pathProperties.map((i) =>
    i.map(extractPropertyValues)
  );

  const height = helperFindProperty(svgPropertiesValues, 'height');
  const width = helperFindProperty(svgPropertiesValues, 'width');
  const paths = pathPropertiesValues.map((i) => ({
    color: helperFindProperty(i, 'stroke'),
    thickness: helperFindProperty(i, 'stroke-width'),
    opacity: helperFindProperty(i, 'opacity'),
    path: helperFindProperty(i, 'd'),
    data: helperFindProperty(i, 'd')
      .match(dRegex)
      .filter((i) => i != '')
      .reduce((acc, elem, index) => {
        if (index % 2 == 0 && index % 4 == 0) {
          acc.push([elem]);
        } else if ((index - 1) % 2 == 0 && (index - 1) % 4 == 0) {
          acc[acc.length - 1].push(elem);
        }

        return acc;
      }, []),
  }));

  return { height, width, paths };
};

console.log(
  convertSvgToPath(
    `<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20" viewBox="0 0 40 20"><path d="M123.234,1.2 L123.234,1.2 M1.234,2.2 L1.234,2.2 M2.234,3.3 L2.234,3.3" stroke="#457429" stroke-width="3" opacity="1" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>`
  )
);

Which should give you something like this:

{
  "height": "20",
  "width": "40",
  "paths": [
    {
      "color": "#457429",
      "thickness": "3",
      "opacity": "1",
      "path": "M123.234,1.2 L123.234,1.2 M1.234,2.2 L1.234,2.2 M2.234,3.3 L2.234,3.3",
      "data": [
        [
          "123.234",
          "1.2"
        ],
        [
          "1.234",
          "2.2"
        ],
        [
          "2.234",
          "3.3"
        ]
      ]
    }
  ]
}

Then you could pass paths to the initialValues props of Draw.

BenJeau commented 2 years ago

Just added a new ref function getSvg() which will properly create an SVG string for the drawing (the first example code gave wrong SVG code - the function in the package should be fine).

I will close this issue as it's been fixed by #29 and as for deserializing an SVG string to PathType[], I think that is something you will have to create on your end (I'm not even sure if its feasible after trying to implement something like that above)