Skip to content

Commit cd8693b

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 bcbb141 commit cd8693b

File tree

5 files changed

+51
-17
lines changed

5 files changed

+51
-17
lines changed

packages/react-sketch-canvas/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ const Canvas = class extends React.Component {
128128
| svgStyle | PropTypes.object | {} | Add CSS styling as CSS-in-JS object for the SVG |
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.) |
131+
| getSvgPathFromPoints | PropTypes.func | undefined | Custom path generator callback that receives the current path as an array and returns an SVG path |
131132

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

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
7070
svgStyle = {},
7171
withViewBox = false,
7272
readOnly = false,
73+
getSvgPathFromPoints = undefined,
7374
} = props;
7475

7576
const canvasRef = React.useRef<HTMLDivElement>(null);
@@ -263,7 +264,7 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
263264
}));
264265

265266
/* Add event listener to Mouse up and Touch up to
266-
release drawing even when point goes out of canvas */
267+
release drawing even when point goes out of canvas */
267268
React.useEffect(() => {
268269
document.addEventListener("pointerup", handlePointerUp);
269270
return () => {
@@ -408,7 +409,11 @@ release drawing even when point goes out of canvas */
408409
key={`${id}__stroke-group-${i}`}
409410
mask={`${eraserPaths[i] && `url(#${id}__eraser-mask-${i})`}`}
410411
>
411-
<Paths id={`${id}__stroke-group-${i}__paths`} paths={pathGroup} />
412+
<Paths
413+
id={`${id}__stroke-group-${i}__paths`}
414+
paths={pathGroup}
415+
getSvgPathFromPoints={getSvgPathFromPoints}
416+
/>
412417
</g>
413418
))}
414419
</svg>

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

Lines changed: 12 additions & 15 deletions
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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const ReactSketchCanvas = React.forwardRef<
4545
withTimestamp = false,
4646
withViewBox = false,
4747
readOnly = false,
48+
getSvgPathFromPoints = undefined,
4849
} = props;
4950

5051
const svgCanvas = React.createRef<CanvasRef>();
@@ -251,6 +252,7 @@ export const ReactSketchCanvas = React.forwardRef<
251252
onPointerUp={handlePointerUp}
252253
withViewBox={withViewBox}
253254
readOnly={readOnly}
255+
getSvgPathFromPoints={getSvgPathFromPoints}
254256
/>
255257
);
256258
});

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

Lines changed: 29 additions & 0 deletions
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)