คลุมข้อความได้ทุก tool ที่ไม่ได้วาด: comment/textbox/stamp ลากคลุมได้ selection สีฟ้าเหมือน tool เลือกข้อความ (ปล่อยเมาส์จากการลากไม่วางรายการ)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
DESKTOP-1R2VSQH\Lenovo ThinkPad E490 2026-09-11 17:41:58 +07:00
parent ca74fb1843
commit 9e5083dae1
2 changed files with 30 additions and 9 deletions

View file

@ -23,6 +23,9 @@ paths:
- DRAW วาดเส้นตรง: **ปล่อยเมาส์แล้ว snap อัตโนมัติ** เมื่อจุดทุกจุดห่างจากเส้นตรง จุดเริ่ม→จุดสุดท้าย ไม่เกิน max(5% ของความยาว, 3px) และเส้นยาว ≥ 24px (`isNearlyStraight`) — หรือกดค้าง **Shift** ระหว่างลาก (กด/ปล่อยกลางทางได้ — listener ระดับ window + reset ตอน blur) เพื่อบังคับตรงเสมอ → บันทึกเหลือจุดเริ่ม→จุดสุดท้าย 2 จุด (ความหนา uniform — ไม่เก็บ `widths` จาก pressure) — ใช้ได้เฉพาะโหมดวาดอิสระ ไม่ใช่ shape/eraser/redact; เส้นโค้ง/ขดจริงหลุดเกณฑ์จึงเหลือ freehand
- Tool `pan` (มือเลื่อนหน้า — ปุ่มแยกถัดจาก select ในแถบ tool): ลากบน `<main>` = `window.scrollBy` สวนทางการลาก + cursor grab/grabbing; ระหว่างใช้ pan ทุก overlay ปิด pointer-events ผ่านตัวแปร `overlayFrozen` (`inkActive || pan`) **ยกเว้น InkLayer ต้องไม่ active** (ไม่งั้น pan กลายเป็นวาดเส้น)
- HIGHLIGHT มี `style?` 4 แบบ: highlight ทึบเหมือนเดิม / underline = เส้นที่ขอบล่าง rect ต่อบรรทัด / strikethrough = เส้นกลางบรรทัด / squiggle = svg path ขยุกขยิก (`squigglePath`) ที่ขอบล่าง — export วาด line/sine แทน rect ทึบ; สร้างได้จาก **tool "highlight" ทางเดียว** (dropdown บนปุ่ม = รูปแบบ + สี → คลุมข้อความ = สร้างทันที พร้อม live preview ผ่าน `selectionchange` + ซ่อน native selection) — tool "เลือกข้อความ" คลุมแล้วเกิด popup ไม่ได้แล้ว (ถอดออก) และคีย์ลัด `H` สลับไป tool highlight
- **คลุมข้อความได้ 5 tool** (`TEXT_SELECTION_TOOLS` set ใน PDFViewer: select/highlight/comment/textbox/stamp — ที่เหลือ textLayer ปิด pointer-events คลุมไม่ได้): ทั้งหมด inject `<style>` ซ่อน native selection + ฟัง `selectionchange` วาดของเราเอง (highlight = preview มาร์ก, ที่เหลือ = selection สีฟ้า) + tracking เส้นทางลาก (`selDragStart/EndRef` ที่ mousedown/move) + Ctrl+C กรองข้อความตามเส้นทาง — comment/textbox/stamp ปล่อยเมาส์จากการลากคลุม**ไม่วางรายการ** (guard `!selection.isCollapsed` ต้น `handlePageClick`: click ที่ยิงต่อจาก drag คลุมต้องเฉยไป เหมือน tool เลือกข้อความ คลิกล้วน ๆ ต่างหากที่เปิด composer/วางตรายาง)
- กรอบ selection (F1) ของทุกชนิดที่เป็นรายการของตัวเองมี**แผงเครื่องมือ** (`w-60` เหมือน dropdown ของ tool) ที่ใช้**ตัวควบคุมชุดเดียวกับ dropdown ของ tool ผู้สร้าง** (จาก `components/pdf/ToolControls.tsx` — ColorSwatches/NumberCombo/PanelRow แชร์กับ PDFViewer) ตามชนิด: HIGHLIGHT = สี+รูปแบบมาร์ก, DRAW/SHAPE = สี+ความหนา (×10), STAMP = สี+ขนาด, TEXTBOX = สี+ขนาด+ความทึบ+ตัวหนา+จัดแนว — ปิดท้ายด้วย **ปุ่มลบ** (`onDelete`) เสมอ; ชนิดที่ไม่มีสไตล์ให้แก้ (REDACTION/COMMENT) ได้อย่างน้อยปุ่มลบ — **HIGHLIGHT ไม่มี handle ย้าย/ขยาย** (ผูกกับตำแหน่งข้อความจริง เลือกเพื่อเปลี่ยนสี/รูปแบบ/ลบได้อย่างเดียว)
- TEXTBOX (F17) `position` เป็น**มุมซ้ายบนของกล่องเสมอ** — `align` จัดข้อความ*ใน*กล่องอย่างเดียว (`textAlign`, แบบ Smallpdf: กดจัดแนวแล้วกล่องไม่ขยับ ขยับแค่ข้อความข้างใน — เคยทดลองให้ align เป็น "จุดยึด" ที่ขยับทั้งกล่องแล้วถอดออกเพราะผู้ใช้รู้สึกว่ากล่องเด้ง); ความกว้างสูงสุดจาก `textboxMaxWidthPct(x)` (`utils/geometry.ts`) บีบตามพื้นที่จริงถึงขอบขวา (cap 50% ของหน้า, ขั้นต่ำ 1%) **กันข้อความล้นขอบกระดาษ** — ใช้สูตรเดียวกันทั้ง render (`TextboxStampLayer`), bbox กรอบ selection/hit-test (`annotationBBox`) และ export PDF
- TEXTBOX กล่องกว้างคงที่ (`width?` สไตล์ Smallpdf/Acrobat): กล่องที่สร้างใหม่คลิกเดียวได้ `width = min(TEXTBOX_DEFAULT_WIDTH_PCT=30, textboxMaxWidthPct)` — **ลากขอบซ้าย/ขวาของกรอบ selection = ปรับความกว้าง** (mode `wleft`/`wright` ใน AnnotationSelectionLayer: ขอบซ้ายขยับ = เลื่อนกล่อง+เปลี่ยนกว้าง (`position.x` = ขอบซ้ายตรง ๆ), ขอบขวาขยับ = เปลี่ยนกว้างอย่างเดียว, clamp กว้าง 2–50% และอยู่ในหน้า) ฟอนต์คงขนาด ข้อความ wrap ใหม่ ส่วนสูงตามจำนวนบรรทัดอัตโนมัติ — กล่องแบบนี้**ซ่อนมุมกรอบ** (scale ไม่มีความหมาย) ย้ายกล่องใช้ขอบบน/ล่าง; กล่องเก่าที่ไม่มี `width` = shrink-to-fit หุ้มข้อความตามพฤติกรรมเดิมทั้งหมด
- คลิกเลือกบนหน้า: tool select/highlight คลิกบน annotation = `setSelectedId` ผ่าน `hitTestAnnotation` (DRAW/SHAPE ใช้ hit-test แม่นเดียวกับ eraser, HIGHLIGHT ดู rect, ที่เหลือ bbox+padding) — เช็คจากรายการหลังสุดก่อน (ตัวบนสุดชนะ), คลิกที่ว่าง = deselect; ใช้ `visibleAnnotations` (เคารพ filter รายคน F4)
- Tooltip hover (N1): `onMouseMove` ของ page wrapper ทำ hit-test ชุดเดียวกับคลิก → `hoveredId` state **เดี่ยวระดับ root เปลี่ยนเฉพาะเมื่อ id เปลี่ยน** (ห้าม setState ทุก mousemove, hit-test เดินจากท้าย array ไม่ allocate ต่อ event) → tooltip "ชนิด · ผู้ทำ + โน้ต" ลอยข้าง bbox (`pointer-events: none` เสมอ, z-[5], ยกเว้น COMMENT) — ปิดช่วง tool ที่รับ pointer (`draw/shape/erase/redact/pan`), `e.buttons !== 0` และตอน `openPinId !== null` (popover ของ pin เปิดอยู่)