ซ่อม text ไทย PDF (ActualText) + revalidatePath ทุก action + คีย์ลัด DEL + ตรายาง label บรรทัดเดียว
- โหลด PDF ผ่าน /api/pdf?fileId= ที่สร้าง ToUnicode CMap ใหม่บนสำเนาในหน่วยความจำ จาก ActualText ใน marked content (lib/pdf-actual-text.ts + unit tests) — แก้ ฟอนต์ไทยที่ cmap เพี้ยน (คำสั่งศาล) ให้ textLayer select/copy/ค้นหา/ชื่อ bookmark ถูกต้องตั้งแต่ต้น ไฟล์ใน public/ ไม่ถูกแตะ (invariant #1) — PDF ปกติได้ bytes เดิม ไม่มี cost และ fail แล้ว PDFViewer fallback fetch ไฟล์ดิบเอง - ข้อความจาก selection ห้าม selection.toString() ตรง ๆ: ใช้ selectionTextWithin เดินทีละอักษร เรียงตามตำแหน่งบนจอ (จัดการ Thai combining marks + ctrl ขยะ) ปิดท้ายด้วย sanitizePdfText ก่อนเก็บ DB/ชื่อ bookmark/คลิปบอร์ด (มี tests) - mergeRectsByLine ใช้ median ความสูง rect คำนวณเกณฑ์บรรทัดแทนค่าคงที่ - Server Actions ทุกตัวที่เขียน DB เรียก revalidateFilePages (revalidatePath หน้า viewer+summary) คู่กับ publish event — แก้กด back แล้วเข้าไฟล์ใหม่ได้ RSC payload เก่าจาก Router Cache ฝั่ง client จนต้อง hard refresh - คีย์ลัด DEL/Backspace ลบ annotation ที่เลือกอยู่ (ของตัวเองเท่านั้น ข้ามเมื่อ focus อยู่ใน input/textarea/select/contenteditable) - STAMP: ย่อฟอนต์ label ให้อยู่บรรทัดเดียวพอดีกรอบทั้งบนจอ (canvas measure) และ export PDF (stampFitFontSize แชร์กัน) + เพิ่ม handle ปรับขนาดตรายางในแผง selection (uniform scale + clamp รายแกน) - sync invariant/กติกา: CLAUDE.md #4, rules/*.md, PLAN-FEAT.md (F3) Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
289d735bf3
commit
6fd3a1515f
19 changed files with 890 additions and 53 deletions
|
|
@ -5,7 +5,7 @@ import { useEffect, useMemo, useRef } from "react";
|
|||
import type { StampData, TextboxData, TextboxSegment } from "@/db/schema";
|
||||
import { toPx, type Point, type Size } from "@/utils/coordinates";
|
||||
import { STAMP_HEIGHT_RATIO, TEXTBOX_MAX_WIDTH_PCT } from "@/utils/geometry";
|
||||
import { STAMP_COLOR, STAMP_FONT_RATIO } from "@/utils/annotation-display";
|
||||
import { STAMP_COLOR, stampFitFontSize } from "@/utils/annotation-display";
|
||||
|
||||
// TEXTBOX + STAMP overlay (F17/B2, F18/B5) — HTML overlay (z-4 กลุ่มเดียวกับ comment pins)
|
||||
// - container `pointer-events: none` เสมอ เฉพาะรายการของตัวเองที่ `auto`
|
||||
|
|
@ -20,6 +20,20 @@ export type InkTextbox = { id: string; userId: string; data: TextboxData };
|
|||
/** ตรายางจาก DB (F18/B5) — พิกัด % ทั้งหมด */
|
||||
export type InkStamp = { id: string; userId: string; data: StampData };
|
||||
|
||||
// ฟอนต์ตรายางบนจอ — ตั้งชัดทั้งตอนวัด (canvas) และตอน render เพื่อให้ความกว้าง
|
||||
// ที่ stampFitFontSize คำนวณตรงกับที่แสดงจริง (ตัวหนา 700 เหมือน class font-bold)
|
||||
const STAMP_FONT_STACK = "system-ui, sans-serif";
|
||||
let stampMeasureCtx: CanvasRenderingContext2D | null = null;
|
||||
|
||||
/** วัดความกว้างข้อความตรายางบนจอ — ไม่มี document (SSR) คืน 0 ให้ใช้ขนาด base */
|
||||
function measureStampText(text: string, fontSize: number): number {
|
||||
if (typeof document === "undefined") return 0;
|
||||
stampMeasureCtx ??= document.createElement("canvas").getContext("2d");
|
||||
if (!stampMeasureCtx) return 0;
|
||||
stampMeasureCtx.font = `bold ${fontSize}px ${STAMP_FONT_STACK}`;
|
||||
return stampMeasureCtx.measureText(text).width;
|
||||
}
|
||||
|
||||
/** แปลงข้อความ/segments → HTML สำหรับ contentEditable (escape + <b> + <br>) */
|
||||
function segmentsToHtml(text: string, segments?: TextboxSegment[]): string {
|
||||
const esc = (s: string) =>
|
||||
|
|
@ -294,7 +308,8 @@ export default function TextboxStampLayer({
|
|||
onCancel={onCancelComposer}
|
||||
/>
|
||||
)}
|
||||
{/* STAMP (F18/B5) — กรอบข้อความสไตล์ตรายาง: กว้าง = scale% ของหน้า สูง = 0.4 เท่า (§5) */}
|
||||
{/* STAMP (F18/B5) — กรอบข้อความสไตล์ตรายาง: กว้าง = scale% ของหน้า สูง = 0.4 เท่า (§5)
|
||||
ฟอนต์ย่อให้ label อยู่บรรทัดเดียวเสมอ (ยาวอย่าง "ตรวจสอบแล้ว" ไม่ wrap/ถูกตัด) */}
|
||||
{stamps.map((s) => {
|
||||
const w = toPx(s.data.scale, size.width);
|
||||
const c = s.data.color ?? STAMP_COLOR; // แถวเดิมไม่มี color = สีตรายาง default
|
||||
|
|
@ -310,8 +325,10 @@ export default function TextboxStampLayer({
|
|||
border: `${Math.max(w * 0.02, 1)}px solid ${c}`,
|
||||
borderRadius: Math.max(w * 0.04, 2),
|
||||
color: c,
|
||||
fontSize: w * STAMP_FONT_RATIO,
|
||||
fontFamily: STAMP_FONT_STACK,
|
||||
fontSize: stampFitFontSize(s.data.label, w, measureStampText),
|
||||
lineHeight: 1.2,
|
||||
whiteSpace: "nowrap",
|
||||
pointerEvents: enabled ? "auto" : "none",
|
||||
cursor: enabled ? "pointer" : "default",
|
||||
}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue