Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 7 additions & 34 deletions packages/pointer-native-drawing/src/DrawingCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,13 @@ import { Gesture, GestureDetector, PointerType } from 'react-native-gesture-hand
import { runOnJS, useSharedValue, useDerivedValue } from 'react-native-reanimated';

import { buildSmoothPath } from './smoothing';

export type Point = { x: number; y: number };
export type Stroke = { points: Point[]; color: string; width: number };
export type TextItem = {
id: string;
text: string;
x: number;
y: number;
fontSize: number;
color: string;
};
export type DrawingCanvasRef = {
clear: () => void;
undo: () => void;
redo: () => void;
canUndo: () => boolean;
canRedo: () => boolean;
getStrokes: () => Stroke[];
setStrokes: (strokes: Stroke[]) => void;
getTexts: () => TextItem[];
setTexts: (texts: TextItem[]) => void;
};
import {
type Point,
type Stroke,
type TextItem,
type DrawingCanvasRef,
} from './model/drawingTypes';
import { deepCopyStrokes, deepCopyTexts, safeMax } from './model/strokeUtils';

type Props = {
strokeColor?: string;
Expand All @@ -66,18 +51,6 @@ type Props = {
textFontPath?: number; // Skia에서 사용할 폰트 파일 경로 (require로 전달)
};

const deepCopyStrokes = (strokes: Stroke[]): Stroke[] =>
strokes.map((stroke) => ({
points: stroke.points.map((p) => ({ ...p })),
color: stroke.color,
width: stroke.width,
}));

const deepCopyTexts = (texts: TextItem[]): TextItem[] => texts.map((text) => ({ ...text }));

const safeMax = (arr: number[], fallback = 0): number =>
arr.length > 0 ? arr.reduce((max, v) => (v > max ? v : max), arr[0]) : fallback;

const DrawingCanvas = forwardRef<DrawingCanvasRef, Props>(
(
{
Expand Down
6 changes: 5 additions & 1 deletion packages/pointer-native-drawing/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export { default as DrawingCanvas } from './DrawingCanvas';
export type { DrawingCanvasRef, Point, Stroke, TextItem } from './DrawingCanvas';
export { buildSmoothPath } from './smoothing';

// model
export type { Point, Stroke, TextItem, DrawingCanvasRef } from './model/drawingTypes';
export type { ReadonlyPoint, ReadonlyStroke, StrokeBounds } from './model/drawingTypes';
export { deepCopyStrokes, deepCopyTexts, safeMax, computeStrokeBounds } from './model/strokeUtils';
52 changes: 52 additions & 0 deletions packages/pointer-native-drawing/src/model/drawingTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// ── 기본 타입 ──

export type Point = { x: number; y: number };

export type Stroke = {
points: Point[];
color: string;
width: number;
};

export type TextItem = {
id: string;
text: string;
x: number;
y: number;
fontSize: number;
color: string;
};

// ── Readonly 타입 (불변 참조용) ──

export type ReadonlyPoint = Readonly<Point>;

export type ReadonlyStroke = {
readonly points: readonly ReadonlyPoint[];
readonly color: string;
readonly width: number;
};

// ── Bounds ──

/** stroke의 AABB (axis-aligned bounding box). 지우개 히트 테스트 최적화 기반. */
export type StrokeBounds = {
readonly minX: number;
readonly minY: number;
readonly maxX: number;
readonly maxY: number;
};

// ── 컴포넌트 공개 API ──

export type DrawingCanvasRef = {
clear: () => void;
undo: () => void;
redo: () => void;
canUndo: () => boolean;
canRedo: () => boolean;
getStrokes: () => Stroke[];
setStrokes: (strokes: Stroke[]) => void;
getTexts: () => TextItem[];
setTexts: (texts: TextItem[]) => void;
};
37 changes: 37 additions & 0 deletions packages/pointer-native-drawing/src/model/strokeUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { type Point, type Stroke, type TextItem, type StrokeBounds } from './drawingTypes';

// ── Deep copy ──

export const deepCopyStrokes = (strokes: Stroke[]): Stroke[] =>
strokes.map((stroke) => ({
points: stroke.points.map((p) => ({ ...p })),
color: stroke.color,
width: stroke.width,
}));

export const deepCopyTexts = (texts: TextItem[]): TextItem[] => texts.map((text) => ({ ...text }));

// ── 배열 유틸 ──

export const safeMax = (arr: number[], fallback = 0): number =>
arr.length > 0 ? arr.reduce((max, v) => (v > max ? v : max), arr[0]) : fallback;

// ── Bounds 계산 ──

/** single-pass O(n)으로 stroke의 AABB를 계산. MAT-360 지우개 히트 테스트 최적화 기반. */
export function computeStrokeBounds(points: readonly Point[]): StrokeBounds {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;

for (let i = 0; i < points.length; i++) {
const p = points[i];
if (p.x < minX) minX = p.x;
if (p.y < minY) minY = p.y;
if (p.x > maxX) maxX = p.x;
if (p.y > maxY) maxY = p.y;
}

return { minX, minY, maxX, maxY };
}
2 changes: 1 addition & 1 deletion packages/pointer-native-drawing/src/smoothing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Skia, type SkPath } from '@shopify/react-native-skia';

type Point = { x: number; y: number };
import { type Point } from './model/drawingTypes';

export function buildSmoothPath(points: Point[]): SkPath {
const path = Skia.Path.Make();
Expand Down
Loading