Skip to content

Commit 2a6b5b4

Browse files
committed
Add custom paths feature
Fixes #143 Add support for custom path generator in `ReactSketchCanvas`. * Add `getSvgPathFromPoints` prop to `ReactSketchCanvas` component to accept a callback function for custom path generation. * Pass `getSvgPathFromPoints` prop to `Canvas` component. * Add `getSvgPathFromPoints` prop to `Canvas` component to accept a callback function for custom path generation. * Pass `getSvgPathFromPoints` prop to `SvgPath` component. * Modify `SvgPath` component to use the custom path generator if provided. * Update documentation in `README.md` to include the new `getSvgPathFromPoints` prop and its usage. * Add tests in `export.spec.tsx` to verify the functionality of the custom path generator. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/vinothpandian/react-sketch-canvas/issues/143?shareId=XXXX-XXXX-XXXX-XXXX).
1 parent ef93338 commit 2a6b5b4

File tree

7 files changed

+57
-21
lines changed

7 files changed

+57
-21
lines changed

packages/react-sketch-canvas/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ const Canvas = class extends React.Component {
129129
| withTimestamp | PropTypes.bool | false | Add timestamp to individual strokes for measuring sketching time |
130130
| readOnly | PropTypes.bool | false | Disable drawing on the canvas (undo/redo, clear & reset will still work.) |
131131
| throttleTime | PropTypes.number | 0 | Throttle time for pointer move events in milliseconds |
132+
| getSvgPathFromPoints | PropTypes.func | | Custom path generator callback that receives the current path as an array and returns an SVG path |
132133

133134
Set SVG background using CSS [background][css-bg] value
134135

packages/react-sketch-canvas/src/Canvas/index.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
7272
withViewBox = false,
7373
readOnly = false,
7474
throttleTime = 0,
75+
getSvgPathFromPoints,
7576
} = props;
7677

7778
const canvasRef = React.useRef<HTMLDivElement>(null);
@@ -411,7 +412,11 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
411412
key={`${id}__stroke-group-${i}`}
412413
mask={`${eraserPaths[i] && `url(#${id}__eraser-mask-${i})`}`}
413414
>
414-
<Paths id={`${id}__stroke-group-${i}__paths`} paths={pathGroup} />
415+
<Paths
416+
id={`${id}__stroke-group-${i}__paths`}
417+
paths={pathGroup}
418+
getSvgPathFromPoints={getSvgPathFromPoints}
419+
/>
415420
</g>
416421
))}
417422
</svg>

packages/react-sketch-canvas/src/Canvas/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ export interface CanvasProps {
120120
* @defaultValue 0
121121
*/
122122
throttleTime?: number;
123+
/**
124+
* Function to convert points to SVG path.
125+
* @defaultValue A function that converts points to SVG path.
126+
*/
127+
getSvgPathFromPoints?: (points: Point[]) => string;
123128
}
124129

125130
/**

packages/react-sketch-canvas/src/Paths/index.tsx

+12-15
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type ControlPoints = {
1111
type PathProps = {
1212
id: string;
1313
paths: CanvasPath[];
14+
getSvgPathFromPoints?: (points: Point[]) => string;
1415
};
1516

1617
export const line = (pointA: Point, pointB: Point) => {
@@ -78,27 +79,21 @@ export const bezierCommand = (point: Point, i: number, a: Point[]): string => {
7879
};
7980

8081
export type SvgPathProps = {
81-
// List of points to create the stroke
8282
paths: Point[];
83-
// Unique ID
8483
id: string;
85-
// Width of the stroke
8684
strokeWidth: number;
87-
// Color of the stroke
8885
strokeColor: string;
89-
// Bezier command to smoothen the line
9086
command?: (point: Point, i: number, a: Point[]) => string;
87+
getSvgPathFromPoints?: (points: Point[]) => string;
9188
};
9289

93-
/**
94-
* Generate SVG Path tag from the given points
95-
*/
9690
export function SvgPath({
9791
paths,
9892
id,
9993
strokeWidth,
10094
strokeColor,
10195
command = bezierCommand,
96+
getSvgPathFromPoints,
10297
}: SvgPathProps): JSX.Element {
10398
if (paths.length === 1) {
10499
const { x, y } = paths[0];
@@ -118,11 +113,13 @@ export function SvgPath({
118113
);
119114
}
120115

121-
const d = paths.reduce(
122-
(acc, point, i, a) =>
123-
i === 0 ? `M ${point.x},${point.y}` : `${acc} ${command(point, i, a)}`,
124-
"",
125-
);
116+
const d = getSvgPathFromPoints
117+
? getSvgPathFromPoints(paths)
118+
: paths.reduce(
119+
(acc, point, i, a) =>
120+
i === 0 ? `M ${point.x},${point.y}` : `${acc} ${command(point, i, a)}`,
121+
"",
122+
);
126123

127124
return (
128125
<path
@@ -137,18 +134,18 @@ export function SvgPath({
137134
);
138135
}
139136

140-
function Paths({ id, paths }: PathProps): JSX.Element {
137+
function Paths({ id, paths, getSvgPathFromPoints }: PathProps): JSX.Element {
141138
return (
142139
<>
143140
{paths.map((path: CanvasPath, index: number) => (
144141
<SvgPath
145-
// eslint-disable-next-line react/no-array-index-key
146142
key={`${id}__${index}`}
147143
paths={path.paths}
148144
id={`${id}__${index}`}
149145
strokeWidth={path.strokeWidth}
150146
strokeColor={path.strokeColor}
151147
command={bezierCommand}
148+
getSvgPathFromPoints={getSvgPathFromPoints}
152149
/>
153150
))}
154151
</>

packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export const ReactSketchCanvas = React.forwardRef<
4545
withTimestamp = false,
4646
withViewBox = false,
4747
readOnly = false,
48+
throttleTime,
49+
getSvgPathFromPoints,
4850
} = props;
4951

5052
const svgCanvas = React.createRef<CanvasRef>();
@@ -251,6 +253,8 @@ export const ReactSketchCanvas = React.forwardRef<
251253
onPointerUp={handlePointerUp}
252254
withViewBox={withViewBox}
253255
readOnly={readOnly}
256+
throttleTime={throttleTime}
257+
getSvgPathFromPoints={getSvgPathFromPoints}
254258
/>
255259
);
256260
});

packages/react-sketch-canvas/src/ReactSketchCanvas/types.ts

-5
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,6 @@ export interface ReactSketchCanvasProps
6565
* @defaultValue false
6666
*/
6767
withTimestamp?: boolean;
68-
/**
69-
* Throttle time for pointer move events in milliseconds.
70-
* @defaultValue 0
71-
*/
72-
throttleTime?: number;
7368
}
7469

7570
/**

packages/tests/src/actions/export.spec.tsx

+29
Original file line numberDiff line numberDiff line change
@@ -431,3 +431,32 @@ test.describe("export SVG", () => {
431431
});
432432
});
433433
});
434+
435+
test.describe("custom path generator", () => {
436+
test("should use custom path generator if provided", async ({ mount }) => {
437+
let svg: string | undefined;
438+
const handleExportSVG = (exportedSvg: string | undefined) => {
439+
svg = exportedSvg;
440+
};
441+
442+
const customPathGenerator = (points: { x: number; y: number }[]) =>
443+
`M${points.map((p) => `${p.x},${p.y}`).join(" ")}`;
444+
445+
const { canvas, exportSVGButton } = await mountCanvasForExport({
446+
mount,
447+
handleExportSVG,
448+
getSvgPathFromPoints: customPathGenerator,
449+
});
450+
451+
const { firstStrokePathId } = getCanvasIds(canvasId);
452+
453+
await exportSVGButton.click();
454+
expect(svg).not.toContain(firstStrokePathId.slice(1));
455+
456+
await drawSquares(canvas);
457+
458+
await exportSVGButton.click();
459+
expect(svg).toContain(firstStrokePathId.slice(1));
460+
expect(svg).toContain("M");
461+
});
462+
});

0 commit comments

Comments
 (0)