zenozeng / svgcanvas

Draw on SVG using Canvas's 2D Context API
MIT License
66 stars 19 forks source link

React Native support? #26

Open keithhilen opened 1 year ago

keithhilen commented 1 year ago

The example does not work in React Native because the document object is not available. Is there a workaround?

Code:

import { Context } from "svgcanvas";

export default () => {
  const ctx = new Context(500, 500); // Error occurs here
}

Error:

ERROR  ReferenceError: Property 'document' doesn't exist

This error is located at:
    in _default (created by _default)
    in RCTSafeAreaView (created by _default)
    in _default (created by _default)
    in _default
    in RCTView (created by View)
    in View (created by AppContainer)
    in RCTView (created by View)
    in View (created by AppContainer)
    in AppContainer
    in TestSvg(RootComponent), js engine: hermes

Versions:

    "react": "18.2.0",
    "react-native": "0.71.3",
    "svgcanvas": "^2.5.0"
arjun-rawal commented 1 year ago

Yes, use react-native-canvas instead of svgcanvas. This is a good tutorial: https://www.atomlab.dev/tutorials/react-native-canvas

keithhilen commented 1 year ago

Thanks for your response, Arjun. I found react-native-svg is a good solution for my use case. I am generating screens with a mixture of text and vector graphics, and I need the ability to pan and zoom with crisp rendering at any scale. It turns out that converting SVG to JSX is straightforward, and the viewport provides pan and zoom. My one remaining stumbling block is how to wrap text in boxes. This is trivial when using components and styles, but I haven't found a good SVG solution.

arjun-rawal commented 1 year ago

This may not be the answer you're looking for, but wrapping your text in a view component and styling it with a border might work.

keithhilen commented 1 year ago

Unfortunately no - SVG and CSS rendering are two different universes right now. I believe I'm going to have to find or create an algorithm for wrapping text. As an experiment, and potentially as part of the solution, I did find a way to measure text width. Bit of a kludge, but it works.

keithhilen commented 1 year ago
/*
  Instantiates a transparent 1x1 canvas to use for measuring text
  Sets a global.measureText function
  Optionally calls a listener when ready

  global.measureText accepts a text string to be measured
  and an optional CSS font specification
  It queries for a metrics object
  This is an async function, so it returns a promise

  To use, include the TextMetrics component somewhere in the screen stack
  Invoke the function as global.measureText(text, font).then((metrics) => { ... })
*/

import React, { useState, useEffect, useRef } from 'react';
import Canvas from 'react-native-canvas';

export default (props) => {
  const ref = useRef(null);
  const [listener, setListener] = useState({f: props.listener});
  const [measureText, setMeasureText] = useState();

  useEffect(() => {
    if (ref.current) {
      if (!measureText) {
        const canvas = ref.current;
        const ctx = canvas.getContext('2d');

        setMeasureText({f: 
          async (text, font) => {
            return new Promise(async (resolve, reject) => {

              try {
                var saveFont = ctx.font;
                if (font) {
                  ctx.font = font;
                }
                ctx.measureText(text).then((metrics) => {
                  ctx.font = saveFont;
                  resolve({text: text, metrics: metrics})
                }).catch(err => {
                  ctx.font = saveFont;
                  reject(err);
                })
              } catch(err) {
                reject(err);
              }
            });
          }}
        )
      }
    }
  }, [ref]);

  useEffect(() => {
    if (measureText) {
      global.measureText = measureText.f;
    }
  }, [measureText]);

  useEffect(() => {
    if (measureText && listener) {
      if (listener.f) {
        listener.f();
      }
    }
  }, [measureText, listener]);

  return (
    <Canvas style={{width: 1, height: 1}} ref={ref}/>
  );
}