1 | import { Component } from 'react';
|
2 | import SvgTouchableMixin from '../lib/SvgTouchableMixin';
|
3 | import {
|
4 | NativeModules,
|
5 | findNodeHandle,
|
6 | NativeMethodsMixinStatic,
|
7 | } from 'react-native';
|
8 | import { TransformProps } from '../lib/extract/types';
|
9 |
|
10 | const RNSVGRenderableManager = NativeModules.RNSVGRenderableManager;
|
11 |
|
12 | export interface SVGBoundingBoxOptions {
|
13 | fill?: boolean;
|
14 | stroke?: boolean;
|
15 | markers?: boolean;
|
16 | clipped?: boolean;
|
17 | }
|
18 |
|
19 | export interface DOMPointInit {
|
20 | x?: number;
|
21 | y?: number;
|
22 | z?: number;
|
23 | w?: number;
|
24 | }
|
25 |
|
26 | export interface Point {
|
27 | x: number;
|
28 | y: number;
|
29 | }
|
30 |
|
31 | export interface SVGPoint extends Point {
|
32 | constructor(point?: Point): SVGPoint;
|
33 | matrixTransform(matrix: Matrix): SVGPoint;
|
34 | }
|
35 |
|
36 | export interface Rect {
|
37 | x: number;
|
38 | y: number;
|
39 | width: number;
|
40 | height: number;
|
41 | }
|
42 | export interface SVGRect extends Rect {}
|
43 |
|
44 | export interface Matrix {
|
45 | a: number;
|
46 | b: number;
|
47 | c: number;
|
48 | d: number;
|
49 | e: number;
|
50 | f: number;
|
51 | }
|
52 |
|
53 | export interface SVGMatrix extends Matrix {
|
54 | constructor(matrix?: Matrix): SVGMatrix;
|
55 | multiply(secondMatrix: Matrix): SVGMatrix;
|
56 | inverse(): SVGMatrix;
|
57 | translate(x: number, y: number): SVGMatrix;
|
58 | scale(scaleFactor: number): SVGMatrix;
|
59 | scaleNonUniform(scaleFactorX: number, scaleFactorY: number): SVGMatrix;
|
60 | rotate(angle: number): SVGMatrix;
|
61 | rotateFromVector(x: number, y: number): SVGMatrix;
|
62 | flipX(): SVGMatrix;
|
63 | flipY(): SVGMatrix;
|
64 | skewX(angle: number): SVGMatrix;
|
65 | skewY(angle: number): SVGMatrix;
|
66 | }
|
67 |
|
68 | export function multiply_matrices(l: Matrix, r: Matrix): Matrix {
|
69 | const { a: al, b: bl, c: cl, d: dl, e: el, f: fl } = l;
|
70 | const { a: ar, b: br, c: cr, d: dr, e: er, f: fr } = r;
|
71 |
|
72 | const a = al * ar + cl * br;
|
73 | const c = al * cr + cl * dr;
|
74 | const e = al * er + cl * fr + el;
|
75 | const b = bl * ar + dl * br;
|
76 | const d = bl * cr + dl * dr;
|
77 | const f = bl * er + dl * fr + fl;
|
78 |
|
79 | return { a, c, e, b, d, f };
|
80 | }
|
81 |
|
82 | export function invert({ a, b, c, d, e, f }: Matrix): Matrix {
|
83 | const n = a * d - b * c;
|
84 | return {
|
85 | a: d / n,
|
86 | b: -b / n,
|
87 | c: -c / n,
|
88 | d: a / n,
|
89 | e: (c * f - d * e) / n,
|
90 | f: -(a * f - b * e) / n,
|
91 | };
|
92 | }
|
93 |
|
94 | const deg2rad = Math.PI / 180;
|
95 |
|
96 | export class SVGMatrix implements SVGMatrix {
|
97 | constructor(matrix?: Matrix) {
|
98 | if (matrix) {
|
99 | const { a, b, c, d, e, f } = matrix;
|
100 | this.a = a;
|
101 | this.b = b;
|
102 | this.c = c;
|
103 | this.d = d;
|
104 | this.e = e;
|
105 | this.f = f;
|
106 | } else {
|
107 | this.a = 1;
|
108 | this.b = 0;
|
109 | this.c = 0;
|
110 | this.d = 1;
|
111 | this.e = 0;
|
112 | this.f = 0;
|
113 | }
|
114 | }
|
115 | multiply(secondMatrix: Matrix): SVGMatrix {
|
116 | return new SVGMatrix(multiply_matrices(this, secondMatrix));
|
117 | }
|
118 | inverse(): SVGMatrix {
|
119 | return new SVGMatrix(invert(this));
|
120 | }
|
121 | translate(x: number, y: number): SVGMatrix {
|
122 | return new SVGMatrix(
|
123 | multiply_matrices(this, { a: 1, b: 0, c: 0, d: 1, e: x, f: y }),
|
124 | );
|
125 | }
|
126 | scale(scaleFactor: number): SVGMatrix {
|
127 | return new SVGMatrix(
|
128 | multiply_matrices(this, {
|
129 | a: scaleFactor,
|
130 | b: 0,
|
131 | c: 0,
|
132 | d: scaleFactor,
|
133 | e: 0,
|
134 | f: 0,
|
135 | }),
|
136 | );
|
137 | }
|
138 | scaleNonUniform(scaleFactorX: number, scaleFactorY: number): SVGMatrix {
|
139 | return new SVGMatrix(
|
140 | multiply_matrices(this, {
|
141 | a: scaleFactorX,
|
142 | b: 0,
|
143 | c: 0,
|
144 | d: scaleFactorY,
|
145 | e: 0,
|
146 | f: 0,
|
147 | }),
|
148 | );
|
149 | }
|
150 | rotate(angle: number): SVGMatrix {
|
151 | const cos = Math.cos(deg2rad * angle);
|
152 | const sin = Math.sin(deg2rad * angle);
|
153 | return new SVGMatrix(
|
154 | multiply_matrices(this, { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 }),
|
155 | );
|
156 | }
|
157 | rotateFromVector(x: number, y: number): SVGMatrix {
|
158 | const angle = Math.atan2(y, x);
|
159 | const cos = Math.cos(deg2rad * angle);
|
160 | const sin = Math.sin(deg2rad * angle);
|
161 | return new SVGMatrix(
|
162 | multiply_matrices(this, { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 }),
|
163 | );
|
164 | }
|
165 | flipX(): SVGMatrix {
|
166 | return new SVGMatrix(
|
167 | multiply_matrices(this, { a: -1, b: 0, c: 0, d: 1, e: 0, f: 0 }),
|
168 | );
|
169 | }
|
170 | flipY(): SVGMatrix {
|
171 | return new SVGMatrix(
|
172 | multiply_matrices(this, { a: 1, b: 0, c: 0, d: -1, e: 0, f: 0 }),
|
173 | );
|
174 | }
|
175 | skewX(angle: number): SVGMatrix {
|
176 | return new SVGMatrix(
|
177 | multiply_matrices(this, {
|
178 | a: 1,
|
179 | b: 0,
|
180 | c: Math.tan(deg2rad * angle),
|
181 | d: 1,
|
182 | e: 0,
|
183 | f: 0,
|
184 | }),
|
185 | );
|
186 | }
|
187 | skewY(angle: number): SVGMatrix {
|
188 | return new SVGMatrix(
|
189 | multiply_matrices(this, {
|
190 | a: 1,
|
191 | b: Math.tan(deg2rad * angle),
|
192 | c: 0,
|
193 | d: 1,
|
194 | e: 0,
|
195 | f: 0,
|
196 | }),
|
197 | );
|
198 | }
|
199 | }
|
200 |
|
201 | export function matrixTransform(matrix: Matrix, point: Point): Point {
|
202 | const { a, b, c, d, e, f } = matrix;
|
203 | const { x, y } = point;
|
204 | return {
|
205 | x: a * x + c * y + e,
|
206 | y: b * x + d * y + f,
|
207 | };
|
208 | }
|
209 |
|
210 | export class SVGPoint implements SVGPoint {
|
211 | constructor(point?: Point) {
|
212 | if (point) {
|
213 | const { x, y } = point;
|
214 | this.x = x;
|
215 | this.y = y;
|
216 | } else {
|
217 | this.x = 0;
|
218 | this.y = 0;
|
219 | }
|
220 | }
|
221 | matrixTransform(matrix: Matrix): SVGPoint {
|
222 | return new SVGPoint(matrixTransform(matrix, this));
|
223 | }
|
224 | }
|
225 |
|
226 | export const ownerSVGElement = {
|
227 | createSVGPoint(): SVGPoint {
|
228 | return new SVGPoint();
|
229 | },
|
230 | createSVGMatrix(): SVGMatrix {
|
231 | return new SVGMatrix();
|
232 | },
|
233 | };
|
234 |
|
235 | export default class Shape<P> extends Component<P> {
|
236 | [x: string]: unknown;
|
237 | root: (Shape<P> & NativeMethodsMixinStatic) | null = null;
|
238 | constructor(props: P, context: {}) {
|
239 | super(props, context);
|
240 | SvgTouchableMixin(this);
|
241 | }
|
242 | refMethod: (
|
243 | instance: (Shape<P> & NativeMethodsMixinStatic) | null,
|
244 | ) => void = (instance: (Shape<P> & NativeMethodsMixinStatic) | null) => {
|
245 | this.root = instance;
|
246 | };
|
247 | setNativeProps = (
|
248 | props: Object & {
|
249 | matrix?: [number, number, number, number, number, number];
|
250 | } & TransformProps,
|
251 | ) => {
|
252 | this.root && this.root.setNativeProps(props);
|
253 | };
|
254 | |
255 |
|
256 |
|
257 |
|
258 |
|
259 | getBBox = (options?: SVGBoundingBoxOptions): SVGRect => {
|
260 | const { fill = true, stroke = true, markers = true, clipped = true } =
|
261 | options || {};
|
262 | const handle = findNodeHandle(this.root as Component);
|
263 | return RNSVGRenderableManager.getBBox(handle, {
|
264 | fill,
|
265 | stroke,
|
266 | markers,
|
267 | clipped,
|
268 | });
|
269 | };
|
270 | getCTM = (): SVGMatrix => {
|
271 | const handle = findNodeHandle(this.root as Component);
|
272 | return new SVGMatrix(RNSVGRenderableManager.getCTM(handle));
|
273 | };
|
274 | getScreenCTM = (): SVGMatrix => {
|
275 | const handle = findNodeHandle(this.root as Component);
|
276 | return new SVGMatrix(RNSVGRenderableManager.getScreenCTM(handle));
|
277 | };
|
278 | isPointInFill = (options: DOMPointInit): boolean => {
|
279 | const handle = findNodeHandle(this.root as Component);
|
280 | return RNSVGRenderableManager.isPointInFill(handle, options);
|
281 | };
|
282 | isPointInStroke = (options: DOMPointInit): boolean => {
|
283 | const handle = findNodeHandle(this.root as Component);
|
284 | return RNSVGRenderableManager.isPointInStroke(handle, options);
|
285 | };
|
286 | getTotalLength = (): number => {
|
287 | const handle = findNodeHandle(this.root as Component);
|
288 | return RNSVGRenderableManager.getTotalLength(handle);
|
289 | };
|
290 | getPointAtLength = (length: number): SVGPoint => {
|
291 | const handle = findNodeHandle(this.root as Component);
|
292 | return new SVGPoint(
|
293 | RNSVGRenderableManager.getPointAtLength(handle, { length }),
|
294 | );
|
295 | };
|
296 | }
|
297 | Shape.prototype.ownerSVGElement = ownerSVGElement;
|