Skip to content

Commit

Permalink
Add custom paths feature
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
vinothpandian committed Aug 17, 2024
1 parent bcbb141 commit cd8693b
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 17 deletions.
1 change: 1 addition & 0 deletions packages/react-sketch-canvas/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const Canvas = class extends React.Component {
| svgStyle | PropTypes.object | {} | Add CSS styling as CSS-in-JS object for the SVG |
| withTimestamp | PropTypes.bool | false | Add timestamp to individual strokes for measuring sketching time |
| readOnly | PropTypes.bool | false | Disable drawing on the canvas (undo/redo, clear & reset will still work.) |
| getSvgPathFromPoints | PropTypes.func | undefined | Custom path generator callback that receives the current path as an array and returns an SVG path |

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

Expand Down
9 changes: 7 additions & 2 deletions packages/react-sketch-canvas/src/Canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export const Canvas = React.forwardRef<CanvasRef, CanvasProps>((props, ref) => {
svgStyle = {},
withViewBox = false,
readOnly = false,
getSvgPathFromPoints = undefined,
} = props;

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

/* Add event listener to Mouse up and Touch up to
release drawing even when point goes out of canvas */
release drawing even when point goes out of canvas */
React.useEffect(() => {
document.addEventListener("pointerup", handlePointerUp);
return () => {
Expand Down Expand Up @@ -408,7 +409,11 @@ release drawing even when point goes out of canvas */
key={`${id}__stroke-group-${i}`}
mask={`${eraserPaths[i] && `url(#${id}__eraser-mask-${i})`}`}
>
<Paths id={`${id}__stroke-group-${i}__paths`} paths={pathGroup} />
<Paths
id={`${id}__stroke-group-${i}__paths`}
paths={pathGroup}
getSvgPathFromPoints={getSvgPathFromPoints}
/>
</g>
))}
</svg>
Expand Down
27 changes: 12 additions & 15 deletions packages/react-sketch-canvas/src/Paths/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type ControlPoints = {
type PathProps = {
id: string;
paths: CanvasPath[];
getSvgPathFromPoints?: (points: Point[]) => string;
};

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

export type SvgPathProps = {
// List of points to create the stroke
paths: Point[];
// Unique ID
id: string;
// Width of the stroke
strokeWidth: number;
// Color of the stroke
strokeColor: string;
// Bezier command to smoothen the line
command?: (point: Point, i: number, a: Point[]) => string;
getSvgPathFromPoints?: (points: Point[]) => string;
};

/**
* Generate SVG Path tag from the given points
*/
export function SvgPath({
paths,
id,
strokeWidth,
strokeColor,
command = bezierCommand,
getSvgPathFromPoints,
}: SvgPathProps): JSX.Element {
if (paths.length === 1) {
const { x, y } = paths[0];
Expand All @@ -118,11 +113,13 @@ export function SvgPath({
);
}

const d = paths.reduce(
(acc, point, i, a) =>
i === 0 ? `M ${point.x},${point.y}` : `${acc} ${command(point, i, a)}`,
"",
);
const d = getSvgPathFromPoints
? getSvgPathFromPoints(paths)
: paths.reduce(
(acc, point, i, a) =>
i === 0 ? `M ${point.x},${point.y}` : `${acc} ${command(point, i, a)}`,
"",
);

return (
<path
Expand All @@ -137,18 +134,18 @@ export function SvgPath({
);
}

function Paths({ id, paths }: PathProps): JSX.Element {
function Paths({ id, paths, getSvgPathFromPoints }: PathProps): JSX.Element {
return (
<>
{paths.map((path: CanvasPath, index: number) => (
<SvgPath
// eslint-disable-next-line react/no-array-index-key
key={`${id}__${index}`}
paths={path.paths}
id={`${id}__${index}`}
strokeWidth={path.strokeWidth}
strokeColor={path.strokeColor}
command={bezierCommand}
getSvgPathFromPoints={getSvgPathFromPoints}
/>
))}
</>
Expand Down
2 changes: 2 additions & 0 deletions packages/react-sketch-canvas/src/ReactSketchCanvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const ReactSketchCanvas = React.forwardRef<
withTimestamp = false,
withViewBox = false,
readOnly = false,
getSvgPathFromPoints = undefined,
} = props;

const svgCanvas = React.createRef<CanvasRef>();
Expand Down Expand Up @@ -251,6 +252,7 @@ export const ReactSketchCanvas = React.forwardRef<
onPointerUp={handlePointerUp}
withViewBox={withViewBox}
readOnly={readOnly}
getSvgPathFromPoints={getSvgPathFromPoints}
/>
);
});
Expand Down
29 changes: 29 additions & 0 deletions packages/tests/src/actions/export.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -431,3 +431,32 @@ test.describe("export SVG", () => {
});
});
});

test.describe("custom path generator", () => {
test("should use custom path generator if provided", async ({ mount }) => {
let svg: string | undefined;
const handleExportSVG = (exportedSvg: string | undefined) => {
svg = exportedSvg;
};

const customPathGenerator = (points: { x: number; y: number }[]) =>
`M${points.map((p) => `${p.x},${p.y}`).join(" ")}`;

const { canvas, exportSVGButton } = await mountCanvasForExport({
mount,
handleExportSVG,
getSvgPathFromPoints: customPathGenerator,
});

const { firstStrokePathId } = getCanvasIds(canvasId);

await exportSVGButton.click();
expect(svg).not.toContain(firstStrokePathId.slice(1));

await drawSquares(canvas);

await exportSVGButton.click();
expect(svg).toContain(firstStrokePathId.slice(1));
expect(svg).toContain("M");
});
});

0 comments on commit cd8693b

Please sign in to comment.