From 4f0572c66e5acbf1a3a5a75d254c69cd4fb269cc Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Mon, 5 Aug 2024 14:44:52 +0200 Subject: [PATCH 1/5] feat: add support dataUri for android platform --- apps/test-examples/App.js | 3 +- apps/test-examples/src/Test1318.tsx | 52 ++++++++++++++++++++++++++++ src/ReactNativeSVG.ts | 3 +- src/ReactNativeSVG.web.ts | 3 +- src/utils/fetchData.ts | 53 +++++++++++++++++++++++++++++ src/xml.tsx | 12 +------ 6 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 apps/test-examples/src/Test1318.tsx create mode 100644 src/utils/fetchData.ts diff --git a/apps/test-examples/App.js b/apps/test-examples/App.js index c5a50dc9c..f0ee0ed3e 100644 --- a/apps/test-examples/App.js +++ b/apps/test-examples/App.js @@ -4,6 +4,7 @@ import React from 'react'; import ColorTest from './src/ColorTest'; import PointerEventsBoxNone from './src/PointerEventsBoxNone'; import MountUnmount from './src/MountUnmount'; +import Test1318 from './src/Test1318'; import Test1374 from './src/Test1374'; import Test1442 from './src/Test1442'; import Test1451 from './src/Test1451'; @@ -25,5 +26,5 @@ import Test2327 from './src/Test2327'; import Test2366 from './src/Test2366'; export default function App() { - return ; + return ; } diff --git a/apps/test-examples/src/Test1318.tsx b/apps/test-examples/src/Test1318.tsx new file mode 100644 index 000000000..0a6dd16ec --- /dev/null +++ b/apps/test-examples/src/Test1318.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import {SvgUri} from 'react-native-svg'; +import {SvgCssUri} from 'react-native-svg/css'; + +export default function Test() { + return ( + <> + + + + + + + + ); +} diff --git a/src/ReactNativeSVG.ts b/src/ReactNativeSVG.ts index addd927d7..4aca02df4 100644 --- a/src/ReactNativeSVG.ts +++ b/src/ReactNativeSVG.ts @@ -2,7 +2,6 @@ import Shape from './elements/Shape'; import { AstProps, camelCase, - fetchText, JsxAST, Middleware, parse, @@ -19,6 +18,8 @@ import { XmlState, } from './xml'; +import { fetchText } from './utils/fetchData'; + import { RNSVGCircle, RNSVGClipPath, diff --git a/src/ReactNativeSVG.web.ts b/src/ReactNativeSVG.web.ts index 5117b35fb..ee57bda8e 100644 --- a/src/ReactNativeSVG.web.ts +++ b/src/ReactNativeSVG.web.ts @@ -1,7 +1,6 @@ import { AstProps, camelCase, - fetchText, JsxAST, Middleware, parse, @@ -18,6 +17,8 @@ import { XmlState, } from './xml'; +import { fetchText } from './utils/fetchData'; + export { inlineStyles, loadLocalRawResource, diff --git a/src/utils/fetchData.ts b/src/utils/fetchData.ts new file mode 100644 index 000000000..fa9bb5327 --- /dev/null +++ b/src/utils/fetchData.ts @@ -0,0 +1,53 @@ +import { Buffer } from 'buffer'; + +export function fetchText(uri?: string): Promise { + if (uri && uri.startsWith('data:')) { + return new Promise((resolve) => { + resolve(dataUriToXml(uri)); + }); + } else { + return localFetch(uri); + } +} + +// copy from https://www.npmjs.com/package/data-uri-to-buffer +function dataUriToXml(uri: string) { + if (!/^data:/i.test(uri)) { + throw new TypeError( + '`uri` does not appear to be a Data URI (must begin with "data:")' + ); + } + // strip newlines + uri = uri.replace(/\r?\n/g, ''); + // split the URI up into the "metadata" and the "data" portions + const firstComma = uri.indexOf(','); + if (firstComma === -1 || firstComma <= 4) { + throw new TypeError('malformed data: URI'); + } + // remove the "data:" scheme and parse the metadata + const meta = uri.substring(5, firstComma).split(';'); + let base64 = false; + for (let i = 1; i < meta.length; i++) { + if (meta[i] === 'base64') { + base64 = true; + } + } + + // get the encoded data portion and decode URI-encoded chars + const data = unescape(uri.substring(firstComma + 1)); + if (!base64) { + return data; + } + return Buffer.from(data).toString('base64'); +} + +async function localFetch(uri?: string) { + if (!uri) { + return null; + } + const response = await fetch(uri); + if (response.ok || (response.status === 0 && uri.startsWith('file://'))) { + return await response.text(); + } + throw new Error(`Fetching ${uri} failed with status ${response.status}`); +} diff --git a/src/xml.tsx b/src/xml.tsx index cb72dd058..091d23330 100644 --- a/src/xml.tsx +++ b/src/xml.tsx @@ -1,6 +1,7 @@ import type { ComponentType, ComponentProps } from 'react'; import * as React from 'react'; import { Component, useEffect, useMemo, useState } from 'react'; +import { fetchText } from './utils/fetchData'; import type { SvgProps } from './elements/Svg'; import { tags } from './xmlTags'; @@ -78,17 +79,6 @@ export function SvgXml(props: XmlProps) { } } -export async function fetchText(uri?: string) { - if (!uri) { - return null; - } - const response = await fetch(uri); - if (response.ok || (response.status === 0 && uri.startsWith('file://'))) { - return await response.text(); - } - throw new Error(`Fetching ${uri} failed with status ${response.status}`); -} - export function SvgUri(props: UriProps) { const { onError = err, uri, onLoad, fallback } = props; const [xml, setXml] = useState(null); From 6d9b776d4d58db0952322499610397a19e227dd4 Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Mon, 5 Aug 2024 14:46:15 +0200 Subject: [PATCH 2/5] feat: restore default component --- apps/test-examples/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/test-examples/App.js b/apps/test-examples/App.js index f0ee0ed3e..daef0f2a4 100644 --- a/apps/test-examples/App.js +++ b/apps/test-examples/App.js @@ -26,5 +26,5 @@ import Test2327 from './src/Test2327'; import Test2366 from './src/Test2366'; export default function App() { - return ; + return ; } From e6643c8723e509bb5399262dcfdae889467f60bf Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 13 Aug 2024 11:53:52 +0200 Subject: [PATCH 3/5] chore: remove duplicated examples --- apps/test-examples/src/Test1318.tsx | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/apps/test-examples/src/Test1318.tsx b/apps/test-examples/src/Test1318.tsx index 0a6dd16ec..a2a1331f9 100644 --- a/apps/test-examples/src/Test1318.tsx +++ b/apps/test-examples/src/Test1318.tsx @@ -19,27 +19,6 @@ export default function Test() { 'data:image/svg+xml;utf8,%3Csvg%20width%3D%22300%22%20height%3D%22700%22%20viewBox%3D%2250%2050%20100%20100%22%20preserveAspectRatio%3D%22xMidYMid%20meet%22%3E%3Crect%20x%3D%2250%22%20y%3D%2250%22%20width%3D%22100%22%20height%3D%22100%22%20fill%3D%22yellow%22%3E%3C%2Frect%3E%3C%2Fsvg%3E' } /> - - - Date: Tue, 13 Aug 2024 11:54:16 +0200 Subject: [PATCH 4/5] feat: change dataUriXml function --- src/utils/fetchData.ts | 48 ++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 32 deletions(-) diff --git a/src/utils/fetchData.ts b/src/utils/fetchData.ts index fa9bb5327..ad9b88f15 100644 --- a/src/utils/fetchData.ts +++ b/src/utils/fetchData.ts @@ -1,47 +1,31 @@ -import { Buffer } from 'buffer'; +import { Platform } from 'react-native'; export function fetchText(uri?: string): Promise { - if (uri && uri.startsWith('data:')) { + if ( + uri && + uri.startsWith('data:image/svg+xml;utf8') && + Platform.OS === 'android' + ) { return new Promise((resolve) => { resolve(dataUriToXml(uri)); }); } else { - return localFetch(uri); + return fetchUriData(uri); } } -// copy from https://www.npmjs.com/package/data-uri-to-buffer -function dataUriToXml(uri: string) { - if (!/^data:/i.test(uri)) { - throw new TypeError( - '`uri` does not appear to be a Data URI (must begin with "data:")' - ); - } - // strip newlines - uri = uri.replace(/\r?\n/g, ''); - // split the URI up into the "metadata" and the "data" portions - const firstComma = uri.indexOf(','); - if (firstComma === -1 || firstComma <= 4) { - throw new TypeError('malformed data: URI'); - } - // remove the "data:" scheme and parse the metadata - const meta = uri.substring(5, firstComma).split(';'); - let base64 = false; - for (let i = 1; i < meta.length; i++) { - if (meta[i] === 'base64') { - base64 = true; - } - } - - // get the encoded data portion and decode URI-encoded chars - const data = unescape(uri.substring(firstComma + 1)); - if (!base64) { - return data; +function dataUriToXml(uri: string): string | null { + try { + // decode and remove data:image/svg+xml;utf8, prefix + const xml = decodeURIComponent(uri).split(',').slice(1).join(','); + return xml; + } catch (error) { + console.log('error', error); + return null; } - return Buffer.from(data).toString('base64'); } -async function localFetch(uri?: string) { +async function fetchUriData(uri?: string) { if (!uri) { return null; } From 4673bbfa3e606f35fa4a3ff4d22a2ee2ff230d9d Mon Sep 17 00:00:00 2001 From: bohdanprog Date: Tue, 13 Aug 2024 14:14:56 +0200 Subject: [PATCH 5/5] feat: simplify dataUriToXml function and handle error, move check uri type to main fetchText function --- src/utils/fetchData.ts | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/utils/fetchData.ts b/src/utils/fetchData.ts index ad9b88f15..f08b16ed2 100644 --- a/src/utils/fetchData.ts +++ b/src/utils/fetchData.ts @@ -1,14 +1,11 @@ import { Platform } from 'react-native'; -export function fetchText(uri?: string): Promise { - if ( - uri && - uri.startsWith('data:image/svg+xml;utf8') && - Platform.OS === 'android' - ) { - return new Promise((resolve) => { - resolve(dataUriToXml(uri)); - }); +export async function fetchText(uri?: string): Promise { + if (!uri) { + return null; + } + if (uri.startsWith('data:image/svg+xml;utf8') && Platform.OS === 'android') { + return dataUriToXml(uri); } else { return fetchUriData(uri); } @@ -17,18 +14,13 @@ export function fetchText(uri?: string): Promise { function dataUriToXml(uri: string): string | null { try { // decode and remove data:image/svg+xml;utf8, prefix - const xml = decodeURIComponent(uri).split(',').slice(1).join(','); - return xml; + return decodeURIComponent(uri).split(',').slice(1).join(','); } catch (error) { - console.log('error', error); - return null; + throw new Error(`Decoding ${uri} failed with error: ${error}`); } } -async function fetchUriData(uri?: string) { - if (!uri) { - return null; - } +async function fetchUriData(uri: string) { const response = await fetch(uri); if (response.ok || (response.status === 0 && uri.startsWith('file://'))) { return await response.text();