diff options
Diffstat (limited to 'lib/basic-contract/viewer/AutoSignatureFieldDetector.tsx')
| -rw-r--r-- | lib/basic-contract/viewer/AutoSignatureFieldDetector.tsx | 493 |
1 files changed, 0 insertions, 493 deletions
diff --git a/lib/basic-contract/viewer/AutoSignatureFieldDetector.tsx b/lib/basic-contract/viewer/AutoSignatureFieldDetector.tsx deleted file mode 100644 index 7de8062c..00000000 --- a/lib/basic-contract/viewer/AutoSignatureFieldDetector.tsx +++ /dev/null @@ -1,493 +0,0 @@ -// PDF 텍스트 패턴 기반 자동 서명 필드 생성 시스템 - -interface SignaturePattern { - regex: RegExp; - name: string; - priority: number; - offsetX?: number; // 텍스트로부터 X축 오프셋 - offsetY?: number; // 텍스트로부터 Y축 오프셋 - width?: number; // 서명 필드 너비 - height?: number; // 서명 필드 높이 - } - - interface DetectedSignatureLocation { - pageIndex: number; - text: string; - rect: { - x1: number; - y1: number; - x2: number; - y2: number; - }; - pattern: SignaturePattern; - confidence: number; - } - - class AutoSignatureFieldDetector { - private instance: WebViewerInstance; - private signaturePatterns: SignaturePattern[]; - - constructor(instance: WebViewerInstance) { - this.instance = instance; - this.signaturePatterns = this.initializePatterns(); - } - - private initializePatterns(): SignaturePattern[] { - return [ - // 한국어 패턴들 (우선순위 높음) - { - regex: /서명\s*[::]\s*[_\-\s]{3,}/gi, - name: "한국어_서명_콜론", - priority: 10, - offsetX: 80, // "서명:" 텍스트 오른쪽으로 80px - offsetY: -5, // 약간 위로 - width: 150, - height: 40 - }, - { - regex: /서명란\s*[_\-\s]{0,}/gi, - name: "한국어_서명란", - priority: 9, - offsetX: 60, - offsetY: -5, - width: 150, - height: 40 - }, - { - regex: /서명\s*[_\-\s]{5,}/gi, - name: "한국어_서명_라인", - priority: 8, - offsetX: 50, - offsetY: -5, - width: 150, - height: 40 - }, - { - regex: /(계약자|갑|을)\s*서명\s*[::]?\s*[_\-\s]{0,}/gi, - name: "한국어_계약자_서명", - priority: 9, - offsetX: 100, - offsetY: -5, - width: 150, - height: 40 - }, - - // 영어 패턴들 - { - regex: /signature\s*[::]\s*[_\-\s]{3,}/gi, - name: "영어_signature_콜론", - priority: 8, - offsetX: 120, - offsetY: -5, - width: 150, - height: 40 - }, - { - regex: /sign\s+here\s*[::]?\s*[_\-\s]{0,}/gi, - name: "영어_sign_here", - priority: 9, - offsetX: 100, - offsetY: -5, - width: 150, - height: 40 - }, - { - regex: /sign\s*[::]\s*[_\-\s]{3,}/gi, - name: "영어_sign_콜론", - priority: 7, - offsetX: 60, - offsetY: -5, - width: 150, - height: 40 - }, - - // 날짜와 함께 나오는 패턴들 - { - regex: /날짜\s*[::]\s*[_\-\s]{3,}.*?서명\s*[::]\s*[_\-\s]{3,}/gi, - name: "날짜_서명_조합", - priority: 10, - offsetX: 0, - offsetY: -5, - width: 150, - height: 40 - }, - { - regex: /date\s*[::]\s*[_\-\s]{3,}.*?signature\s*[::]\s*[_\-\s]{3,}/gi, - name: "date_signature_조합", - priority: 10, - offsetX: 0, - offsetY: -5, - width: 150, - height: 40 - }, - - // 일반적인 양식 패턴들 - { - regex: /이름\s*[::]\s*[_\-\s]{5,}.*?서명\s*[::]\s*[_\-\s]{5,}/gi, - name: "이름_서명_조합", - priority: 8, - offsetX: 0, - offsetY: -5, - width: 150, - height: 40 - }, - { - regex: /name\s*[::]\s*[_\-\s]{5,}.*?signature\s*[::]\s*[_\-\s]{5,}/gi, - name: "name_signature_조합", - priority: 8, - offsetX: 0, - offsetY: -5, - width: 150, - height: 40 - } - ]; - } - - // 📄 메인 함수: 문서에서 서명 패턴 감지 및 필드 생성 - async detectAndCreateSignatureFields(): Promise<string[]> { - console.log("🔍 자동 서명 필드 감지 시작..."); - - try { - const { Core } = this.instance; - const { documentViewer } = Core; - - await Core.PDFNet.initialize(); - const doc = await documentViewer.getDocument().getPDFDoc(); - - // 1. 기존 서명 필드 확인 - const existingFields = await this.getExistingSignatureFields(doc); - console.log(`📊 기존 서명 필드: ${existingFields.length}개`); - - if (existingFields.length > 0) { - console.log("✅ 기존 서명 필드가 있으므로 자동 생성 스킵"); - return existingFields.map(f => f.name); - } - - // 2. 텍스트 패턴 기반 서명 위치 감지 - const detectedLocations = await this.detectSignatureLocations(doc); - console.log(`🎯 감지된 서명 위치: ${detectedLocations.length}개`); - - // 3. 감지된 위치에 서명 필드 생성 - const createdFields: string[] = []; - for (const location of detectedLocations) { - try { - const fieldName = await this.createSignatureFieldAtLocation(doc, location); - createdFields.push(fieldName); - console.log(`✅ 서명 필드 생성: ${fieldName}`); - } catch (error) { - console.error(`📛 서명 필드 생성 실패:`, error); - } - } - - // 4. 문서 업데이트 - if (createdFields.length > 0) { - await documentViewer.refreshAll(); - await documentViewer.updateView(); - console.log(`🎉 총 ${createdFields.length}개 서명 필드 자동 생성 완료`); - } else { - console.warn("⚠️ 서명 패턴을 찾지 못했습니다. 기본 서명 필드 생성..."); - const defaultField = await this.createDefaultSignatureField(doc); - createdFields.push(defaultField); - } - - return createdFields; - - } catch (error) { - console.error("📛 자동 서명 필드 생성 실패:", error); - return []; - } - } - - // 기존 서명 필드 확인 - private async getExistingSignatureFields(doc: any): Promise<any[]> { - const { Core } = this.instance; - const fields = []; - - try { - const pageCount = await doc.getPageCount(); - - for (let i = 1; i <= pageCount; i++) { - const page = await doc.getPage(i); - const numAnnots = await page.getNumAnnots(); - - for (let j = 0; j < numAnnots; j++) { - const annot = await page.getAnnot(j); - const annotType = await annot.getType(); - - if (annotType === Core.PDFNet.Annot.Type.e_Widget) { - const widget = await Core.PDFNet.WidgetAnnot.cast(annot); - const field = await widget.getField(); - const fieldType = await field.getType(); - - if (fieldType === Core.PDFNet.Field.Type.e_signature) { - const fieldName = await field.getName(); - fields.push({ name: fieldName, widget, page: i }); - } - } - } - } - } catch (error) { - console.warn("기존 필드 확인 중 에러:", error); - } - - return fields; - } - - // 텍스트 패턴 기반 서명 위치 감지 - private async detectSignatureLocations(doc: any): Promise<DetectedSignatureLocation[]> { - const { Core } = this.instance; - const detectedLocations: DetectedSignatureLocation[] = []; - - try { - const pageCount = await doc.getPageCount(); - - for (let pageNum = 1; pageNum <= pageCount; pageNum++) { - const page = await doc.getPage(pageNum); - - // 텍스트 추출 - const textExtractor = await Core.PDFNet.TextExtractor.create(); - await textExtractor.begin(page); - - // 텍스트와 위치 정보 추출 - const wordList = []; - let line = await textExtractor.getFirstLine(); - - while (line) { - let word = await line.getFirstWord(); - while (word) { - const wordText = await word.getString(); - const wordBox = await word.getBBox(); - - wordList.push({ - text: wordText, - x1: await wordBox.getX1(), - y1: await wordBox.getY1(), - x2: await wordBox.getX2(), - y2: await wordBox.getY2() - }); - - word = await word.getNext(); - } - line = await line.getNext(); - } - - // 전체 페이지 텍스트 조합 - const fullText = wordList.map(w => w.text).join(' '); - - // 패턴 매칭 - for (const pattern of this.signaturePatterns) { - const matches = Array.from(fullText.matchAll(pattern.regex)); - - for (const match of matches) { - // 매치된 텍스트의 위치 찾기 - const matchText = match[0]; - const matchStart = match.index || 0; - - // 대략적인 위치 계산 (개선 가능) - const location = this.calculateTextLocation(wordList, matchStart, matchText.length); - - if (location) { - detectedLocations.push({ - pageIndex: pageNum - 1, - text: matchText, - rect: { - x1: location.x1 + (pattern.offsetX || 0), - y1: location.y1 + (pattern.offsetY || 0), - x2: location.x1 + (pattern.offsetX || 0) + (pattern.width || 150), - y2: location.y1 + (pattern.offsetY || 0) + (pattern.height || 40) - }, - pattern: pattern, - confidence: pattern.priority - }); - - console.log(`🎯 패턴 매치: "${matchText}" (${pattern.name}) 페이지 ${pageNum}`); - } - } - } - } - - // 신뢰도 순으로 정렬 (중복 제거 포함) - return this.deduplicateAndSort(detectedLocations); - - } catch (error) { - console.error("텍스트 패턴 감지 실패:", error); - return []; - } - } - - // 텍스트 위치 계산 (개선된 버전) - private calculateTextLocation(wordList: any[], startIndex: number, length: number): any { - if (wordList.length === 0) return null; - - // 간단한 구현: 첫 번째 단어의 위치 사용 - // 실제로는 더 정교한 텍스트 매칭 필요 - const totalChars = wordList.map(w => w.text).join(' ').length; - const ratio = startIndex / totalChars; - const targetWordIndex = Math.floor(ratio * wordList.length); - - const targetWord = wordList[Math.min(targetWordIndex, wordList.length - 1)]; - return targetWord; - } - - // 중복 제거 및 정렬 - private deduplicateAndSort(locations: DetectedSignatureLocation[]): DetectedSignatureLocation[] { - // 같은 페이지의 너무 가까운 위치들 제거 - const filtered = locations.filter((loc, index) => { - return !locations.slice(0, index).some(prevLoc => - prevLoc.pageIndex === loc.pageIndex && - Math.abs(prevLoc.rect.x1 - loc.rect.x1) < 100 && - Math.abs(prevLoc.rect.y1 - loc.rect.y1) < 50 - ); - }); - - // 신뢰도(우선순위) 순으로 정렬 - return filtered.sort((a, b) => b.confidence - a.confidence); - } - - // 감지된 위치에 서명 필드 생성 - private async createSignatureFieldAtLocation(doc: any, location: DetectedSignatureLocation): Promise<string> { - const { Core } = this.instance; - - const fieldName = `auto_signature_${location.pageIndex + 1}_${Date.now()}`; - const page = await doc.getPage(location.pageIndex + 1); - - // 디지털 서명 필드 생성 - const sigField = await doc.createDigitalSignatureField(fieldName); - - // 서명 위젯 생성 - const rect = await Core.PDFNet.Rect.init( - location.rect.x1, - location.rect.y1, - location.rect.x2, - location.rect.y2 - ); - - const widget = await Core.PDFNet.SignatureWidget.createWithDigitalSignatureField( - doc, rect, sigField - ); - - // 위젯 스타일 설정 - await widget.setBackgroundColor( - await Core.PDFNet.ColorPt.init(0.95, 0.95, 1.0), // 연한 파란색 - 3 // RGB - ); - - await widget.setBorderColor( - await Core.PDFNet.ColorPt.init(0.2, 0.4, 0.8), // 파란색 테두리 - 3 // RGB - ); - - // 페이지에 위젯 추가 - await page.annotPushBack(widget); - - console.log(`✅ 자동 서명 필드 생성: ${fieldName} (패턴: ${location.pattern.name})`); - return fieldName; - } - - // 기본 서명 필드 생성 (패턴을 찾지 못한 경우) - private async createDefaultSignatureField(doc: any): Promise<string> { - const { Core } = this.instance; - - console.log("⚠️ 서명 패턴 미발견, 기본 위치에 서명 필드 생성"); - - const pageCount = await doc.getPageCount(); - const lastPage = await doc.getPage(pageCount); - const pageInfo = await lastPage.getPageInfo(); - const pageWidth = await pageInfo.getWidth(); - const pageHeight = await pageInfo.getHeight(); - - const fieldName = `default_signature_${Date.now()}`; - const sigField = await doc.createDigitalSignatureField(fieldName); - - // 마지막 페이지 하단 중앙에 배치 - const rect = await Core.PDFNet.Rect.init( - pageWidth * 0.3, // 페이지 너비 30% 지점 - pageHeight * 0.1, // 페이지 하단 10% 지점 - pageWidth * 0.7, // 페이지 너비 70% 지점 - pageHeight * 0.2 // 페이지 하단 20% 지점 - ); - - const widget = await Core.PDFNet.SignatureWidget.createWithDigitalSignatureField( - doc, rect, sigField - ); - - await widget.setBackgroundColor( - await Core.PDFNet.ColorPt.init(1.0, 0.95, 0.95), // 연한 핑크색 (주의 표시) - 3 - ); - - await widget.setBorderColor( - await Core.PDFNet.ColorPt.init(0.8, 0.2, 0.2), // 빨간색 테두리 - 3 - ); - - await lastPage.annotPushBack(widget); - - return fieldName; - } - } - - // ✅ BasicContractSignViewer에 통합할 수 있는 함수 - export async function addAutoSignatureFieldsToDocument(instance: WebViewerInstance): Promise<string[]> { - if (!instance) { - console.warn("⚠️ WebViewer 인스턴스가 없습니다."); - return []; - } - - try { - const detector = new AutoSignatureFieldDetector(instance); - const createdFields = await detector.detectAndCreateSignatureFields(); - - if (createdFields.length > 0) { - console.log(`🎉 자동 서명 필드 생성 완료: ${createdFields.join(', ')}`); - } - - return createdFields; - - } catch (error) { - console.error("📛 자동 서명 필드 추가 실패:", error); - return []; - } - } - - // ✅ 문서 로드 후 자동 호출되는 Hook - export function useAutoSignatureFields(instance: WebViewerInstance | null) { - const [signatureFields, setSignatureFields] = React.useState<string[]>([]); - const [isProcessing, setIsProcessing] = React.useState(false); - - React.useEffect(() => { - if (!instance) return; - - const { documentViewer } = instance.Core; - - const handleDocumentLoaded = async () => { - try { - setIsProcessing(true); - console.log("📄 문서 로드 완료, 자동 서명 필드 생성 시작..."); - - // 문서 로드 후 잠시 대기 (안정성을 위해) - await new Promise(resolve => setTimeout(resolve, 1000)); - - const fields = await addAutoSignatureFieldsToDocument(instance); - setSignatureFields(fields); - - } catch (error) { - console.error("📛 자동 서명 필드 처리 실패:", error); - } finally { - setIsProcessing(false); - } - }; - - documentViewer.addEventListener('documentLoaded', handleDocumentLoaded); - - return () => { - documentViewer.removeEventListener('documentLoaded', handleDocumentLoaded); - }; - }, [instance]); - - return { - signatureFields, - isProcessing, - hasSignatureFields: signatureFields.length > 0 - }; - }
\ No newline at end of file |
