Skip to content

Commit d4c3c71

Browse files
Implement paragraph scribbled shape logic
1 parent d3d802a commit d4c3c71

1 file changed

Lines changed: 97 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { forwardRef, useMemo } from 'react';
2+
import { Group, Path, Rect } from 'react-konva';
3+
import { ShapeSizeRestrictions, ShapeType } from '@/core/model';
4+
import { ShapeProps } from '../../shape.model';
5+
import { useShapeProps } from '../../../shapes/use-shape-props.hook';
6+
import { BASIC_SHAPE } from '../../front-components/shape.const';
7+
import { useGroupShapeProps } from '../../mock-components.utils';
8+
import { calculatePath } from '../text-scribbled-shape/text-scribbled.business';
9+
import { fitSizeToShapeSizeRestrictions } from '@/common/utils/shapes';
10+
11+
const MIN_LINE_HEIGHT = 25;
12+
13+
const paragraphScribbledShapeRestrictions: ShapeSizeRestrictions = {
14+
minWidth: 100,
15+
minHeight: MIN_LINE_HEIGHT,
16+
maxWidth: -1,
17+
maxHeight: -1,
18+
defaultWidth: 300,
19+
defaultHeight: 150,
20+
};
21+
22+
export const getParagraphScribbledShapeRestrictions =
23+
(): ShapeSizeRestrictions => paragraphScribbledShapeRestrictions;
24+
25+
const shapeType: ShapeType = 'paragraphScribbled';
26+
27+
export const ParagraphScribbled = forwardRef<any, ShapeProps>((props, ref) => {
28+
const { width, height, id, otherProps, ...shapeProps } = props;
29+
30+
const { stroke } = useShapeProps(otherProps, BASIC_SHAPE);
31+
const commonGroupProps = useGroupShapeProps(
32+
props,
33+
{ width, height },
34+
shapeType,
35+
ref
36+
);
37+
38+
const restrictedSize = fitSizeToShapeSizeRestrictions(
39+
paragraphScribbledShapeRestrictions,
40+
width,
41+
height
42+
);
43+
44+
const { width: restrictedWidth, height: restrictedHeight } = restrictedSize;
45+
46+
// Calculate how many lines fit based on the height
47+
const numLines = Math.max(1, Math.trunc(restrictedHeight / MIN_LINE_HEIGHT));
48+
49+
// Generate one path per line
50+
const paths = useMemo(() => {
51+
return Array.from({ length: numLines }).map((_, i) => {
52+
const lineY = i * MIN_LINE_HEIGHT;
53+
const lineId = `${id}-${i}`;
54+
const rawPath = calculatePath(restrictedWidth, MIN_LINE_HEIGHT, lineId);
55+
56+
// Adjust the path to shift Y coordinate for each line
57+
// 🔍 Step by step:
58+
// The path assumes the text is vertically centered in a block of given height (e.g., 25px).
59+
// If you just drew this path multiple times, all lines would overlap.
60+
// To fix that, we shift the Y coordinate for each point in the path.
61+
//
62+
// Regular expression: /\d+,\d+/g
63+
// Finds all x,y coordinates in the path string (e.g., "10,12", "15,11").
64+
// We split each coordinate, convert y to number, add vertical offset (lineY),
65+
// then reassemble the coordinate string.
66+
const shiftedPath = rawPath.replace(/\d+,\d+/g, match => {
67+
const [xStr, yStr] = match.split(',');
68+
const x = parseFloat(xStr);
69+
const y = parseFloat(yStr) + lineY;
70+
return `${x},${y}`;
71+
});
72+
73+
return shiftedPath;
74+
});
75+
}, [restrictedWidth, restrictedHeight, id]);
76+
77+
return (
78+
<Group {...commonGroupProps} {...shapeProps}>
79+
{paths.map((path, idx) => (
80+
<Path
81+
key={idx}
82+
data={path}
83+
stroke={stroke}
84+
strokeWidth={3}
85+
lineCap="round"
86+
lineJoin="round"
87+
/>
88+
))}
89+
<Rect
90+
width={restrictedSize.width}
91+
height={restrictedSize.height}
92+
stroke={stroke}
93+
strokeWidth={0}
94+
/>
95+
</Group>
96+
);
97+
});

0 commit comments

Comments
 (0)