import type { Point, Rect } from "@/db/schema"; // พิกัดทุกอย่างที่เก็บลง DB เป็น % ของขนาดหน้า PDF (PLAN.md §2, CLAUDE.md invariant #2) // ทิศทางแปลง: px → % = ÷size×100, % → px = ×size÷100 — ผ่านไฟล์นี้เท่านั้น // Point/Rect define ที่ db/schema.ts (PLAN-FEAT.md §1) แล้ว re-export ที่นี่ export type { Rect, Point }; export type Size = { width: number; height: number }; export const toPercent = (px: number, size: number): number => (px / size) * 100; export const toPx = (percent: number, size: number): number => (percent / 100) * size; export const clamp = (value: number, min: number, max: number): number => Math.min(Math.max(value, min), max); export const pointToPercent = (p: Point, size: Size): Point => ({ x: toPercent(p.x, size.width), y: toPercent(p.y, size.height), }); export const pointToPx = (p: Point, size: Size): Point => ({ x: toPx(p.x, size.width), y: toPx(p.y, size.height), }); export const rectToPercent = (r: Rect, size: Size): Rect => ({ x: toPercent(r.x, size.width), y: toPercent(r.y, size.height), width: toPercent(r.width, size.width), height: toPercent(r.height, size.height), }); export const rectToPx = (r: Rect, size: Size): Rect => ({ x: toPx(r.x, size.width), y: toPx(r.y, size.height), width: toPx(r.width, size.width), height: toPx(r.height, size.height), }); export const pathToPercent = (path: Point[], size: Size): Point[] => path.map((p) => pointToPercent(p, size)); export const pathToPx = (path: Point[], size: Size): Point[] => path.map((p) => pointToPx(p, size)); /** พิกัด pointer จาก event เทียบ page element → % (clamp 0–100 เสมอ) */ export function getPointerPercent( e: { clientX: number; clientY: number }, pageEl: HTMLElement, ): Point { const rect = pageEl.getBoundingClientRect(); return { x: clamp(toPercent(e.clientX - rect.left, rect.width), 0, 100), y: clamp(toPercent(e.clientY - rect.top, rect.height), 0, 100), }; } /** * รวบ rects ที่อยู่บรรทัดเดียวกัน (แนวตั้ง overlap กัน = y/height ใกล้กัน) * เหลือ 1 rect ต่อบรรทัด โดย union แกน x ให้ครอบข้อความเดิมทั้งบรรทัด * เรียกทุกครั้งก่อนบันทึก HIGHLIGHT ลง DB กัน JSONB บวม */ export function mergeRectsByLine(rects: Rect[]): Rect[] { const sorted = [...rects].sort((a, b) => a.y - b.y || a.x - b.x); const merged: Rect[] = []; for (const r of sorted) { const last = merged[merged.length - 1]; if (last && r.y < last.y + last.height) { // บรรทัดเดียวกัน → ขยายกรอบเดิมให้ครอบ rect นี้ด้วย const right = Math.max(last.x + last.width, r.x + r.width); last.x = Math.min(last.x, r.x); last.width = right - last.x; const bottom = Math.max(last.y + last.height, r.y + r.height); last.height = bottom - last.y; } else { merged.push({ ...r }); } } return merged; }