summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-07 07:30:30 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-07 07:30:30 +0000
commitb0fe980376fcf1a19ff4b90851ca8b01f378fdc0 (patch)
treef301f63a91accf3705f6043ca7f8173c74879778
parent9230272dfef34360ca9350423cea37cb5bdeae70 (diff)
(임수민) 기본계약서 자동서명 다중 입력 수정
-rw-r--r--lib/basic-contract/viewer/basic-contract-sign-viewer.tsx202
1 files changed, 116 insertions, 86 deletions
diff --git a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
index d1492fdb..eeebae4e 100644
--- a/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
+++ b/lib/basic-contract/viewer/basic-contract-sign-viewer.tsx
@@ -114,12 +114,12 @@ class AutoSignatureFieldDetector {
console.log(`📄 "${searchText}" 텍스트 검색 중...`);
- // 텍스트 검색 및 서명 필드 생성
- const fieldName = await this.createSignatureFieldAtText(searchText);
+ // 텍스트 검색 및 서명 필드 생성 (모든 매치 찾기)
+ const fieldNames = await this.createSignatureFieldAtText(searchText);
- if (fieldName) {
- console.log(`✅ 텍스트 위치에 서명 필드 생성 완료: ${searchText}`);
- return [fieldName];
+ if (fieldNames.length > 0) {
+ console.log(`✅ ${fieldNames.length}개의 "${searchText}" 위치에 서명 필드 생성 완료`);
+ return fieldNames;
} else {
// 텍스트를 찾지 못한 경우 기본 위치에 생성
console.log(`⚠️ "${searchText}" 텍스트를 찾지 못해 기본 위치에 생성`);
@@ -140,74 +140,88 @@ class AutoSignatureFieldDetector {
}
}
- private async createSignatureFieldAtText(searchText: string): Promise<string | null> {
+ private async createSignatureFieldAtText(searchText: string): Promise<string[]> {
const { Core } = this.instance;
const { documentViewer, annotationManager, Annotations } = Core;
const doc = documentViewer.getDocument();
- if (!doc) return null;
+ if (!doc) return [];
try {
const pageCount = documentViewer.getPageCount();
+ const fieldNames: string[] = [];
+ // 모든 페이지에서 모든 매치 찾기
for (let pageNumber = 1; pageNumber <= pageCount; pageNumber++) {
// 1) 페이지 텍스트 로드
const pageText = await doc.loadPageText(pageNumber);
+ if (!pageText) continue;
- // 2) 해당 페이지에서 검색어 위치 찾기 (첫 매치만 사용)
- const startIndex = pageText.indexOf(searchText);
- if (startIndex === -1) continue;
+ // 2) 해당 페이지에서 모든 검색어 위치 찾기
+ let searchIndex = 0;
+ while (true) {
+ const startIndex = pageText.indexOf(searchText, searchIndex);
+ if (startIndex === -1) break; // 더 이상 찾을 수 없으면 다음 페이지로
- const endIndex = startIndex + searchText.length;
+ const endIndex = startIndex + searchText.length;
- // 3) 검색어의 문자 단위 Quads 얻기
- const quads = await doc.getTextPosition(pageNumber, startIndex, endIndex);
- if (!quads || quads.length === 0) continue;
-
- // 첫 글자의 quad만 사용해 대략적인 위치 산출
- const q = quads[0] as any; // PDFTron의 Quad 타입
- const x = Math.min(q.x1, q.x2, q.x3, q.x4);
- const y = Math.min(q.y1, q.y2, q.y3, q.y4);
- const textHeight = Math.abs(q.y3 - q.y1);
-
- // 4) 서명 필드 생성
- const fieldName = `signature_at_text_${Date.now()}`;
- const signatureY = y + textHeight + 5;
-
- // 위치 정보 저장
- this.location = {
- pageNumber,
- x,
- y: signatureY,
- fieldName,
- searchText
- };
-
- const flags = new Annotations.WidgetFlags();
- flags.set('Required', true);
-
- const field = new Core.Annotations.Forms.Field(fieldName, { type: 'Sig', flags });
- const widget = new Annotations.SignatureWidgetAnnotation(field, { Width: 150, Height: 50 });
-
- widget.setPageNumber(pageNumber);
- // 텍스트 바로 아래에 배치 (필요하면 오른쪽 배치로 바꿀 수 있음)
- widget.setX(x);
- widget.setY(signatureY);
- widget.setWidth(150);
- widget.setHeight(50);
+ // 3) 검색어의 문자 단위 Quads 얻기
+ const quads = await doc.getTextPosition(pageNumber, startIndex, endIndex);
+ if (!quads || quads.length === 0) {
+ searchIndex = startIndex + 1;
+ continue;
+ }
- const fm = annotationManager.getFieldManager();
- fm.addField(field);
- annotationManager.addAnnotation(widget);
- annotationManager.drawAnnotationsFromList([widget]);
+ // 첫 글자의 quad만 사용해 대략적인 위치 산출
+ const q = quads[0] as any; // PDFTron의 Quad 타입
+ const x = Math.min(q.x1, q.x2, q.x3, q.x4);
+ const y = Math.min(q.y1, q.y2, q.y3, q.y4);
+ const textHeight = Math.abs(q.y3 - q.y1);
- return fieldName;
+ // 4) 서명 필드 생성
+ const fieldName = `signature_at_text_${Date.now()}_${fieldNames.length}`;
+ const signatureY = y + textHeight + 5;
+
+ // 첫 번째 필드의 위치 정보만 저장 (서명란으로 이동 기능용)
+ if (fieldNames.length === 0) {
+ this.location = {
+ pageNumber,
+ x,
+ y: signatureY,
+ fieldName,
+ searchText
+ };
+ }
+
+ const flags = new Annotations.WidgetFlags();
+ flags.set('Required', true);
+
+ const field = new Core.Annotations.Forms.Field(fieldName, { type: 'Sig', flags });
+ const widget = new Annotations.SignatureWidgetAnnotation(field, { Width: 150, Height: 50 });
+
+ widget.setPageNumber(pageNumber);
+ // 텍스트 바로 아래에 배치
+ widget.setX(x);
+ widget.setY(signatureY);
+ widget.setWidth(150);
+ widget.setHeight(50);
+
+ const fm = annotationManager.getFieldManager();
+ fm.addField(field);
+ annotationManager.addAnnotation(widget);
+ annotationManager.drawAnnotationsFromList([widget]);
+
+ fieldNames.push(fieldName);
+ console.log(`✅ 페이지 ${pageNumber}의 "${searchText}" 위치에 서명 필드 생성 (${fieldNames.length}번째)`);
+
+ // 다음 검색을 위해 인덱스 이동
+ searchIndex = startIndex + 1;
+ }
}
- // 모든 페이지에서 못 찾으면 null
- return null;
+ return fieldNames;
} catch (e) {
console.error('텍스트 기반 서명 필드 생성 중 오류', e);
- return null;
+ return [];
}
}
@@ -269,56 +283,72 @@ const applyBuyerSignatureAutomatically = async (instance: WebViewerInstance) =>
if (!doc) return;
try {
- console.log('🔍 구매자 자동 서명: "삼성중공업_서명란"만 검색');
+ console.log('🔍 구매자 자동 서명: "삼성중공업_서명란" 전체 검색');
const TARGET = '삼성중공업_서명란'; // ✅ 정확히 이 문자열만 허용
const pageCount = documentViewer.getPageCount();
+ let signatureCount = 0;
+ const buyerSignature = await getBuyerSignatureFileWithFallback(); // 기존 프로젝트 함수 그대로 사용
+ if (!buyerSignature?.data?.dataUrl) {
+ console.warn('⚠️ 구매자 서명 이미지가 없습니다.');
+ return;
+ }
+
+ // 모든 페이지에서 모든 "삼성중공업_서명란" 찾기
for (let pageNumber = 1; pageNumber <= pageCount; pageNumber++) {
const pageText = await doc.loadPageText(pageNumber);
if (!pageText) continue;
- const startIndex = pageText.indexOf(TARGET);
- if (startIndex === -1) continue;
+ // 한 페이지에서 여러 개의 매치를 찾기 위해 반복 검색
+ let searchIndex = 0;
+ while (true) {
+ const startIndex = pageText.indexOf(TARGET, searchIndex);
+ if (startIndex === -1) break; // 더 이상 찾을 수 없으면 다음 페이지로
- const endIndex = startIndex + TARGET.length;
+ const endIndex = startIndex + TARGET.length;
- // 문자 쿼드 → 바운딩 박스
- const quads = await doc.getTextPosition(pageNumber, startIndex, endIndex);
- if (!quads?.length) continue;
+ // 문자 쿼드 → 바운딩 박스
+ const quads = await doc.getTextPosition(pageNumber, startIndex, endIndex);
+ if (!quads?.length) {
+ searchIndex = startIndex + 1;
+ continue;
+ }
- const xs: number[] = [], ys: number[] = [];
- quads.forEach(q => { xs.push(q.x1, q.x2, q.x3, q.x4); ys.push(q.y1, q.y2, q.y3, q.y4); });
- const minX = Math.min(...xs), maxY = Math.max(...ys);
+ const xs: number[] = [], ys: number[] = [];
+ quads.forEach((q: any) => { xs.push(q.x1, q.x2, q.x3, q.x4); ys.push(q.y1, q.y2, q.y3, q.y4); });
+ const minX = Math.min(...xs), maxY = Math.max(...ys);
- // 텍스트 바로 아래에 스탬프(서명 이미지) 배치
- const widgetX = minX;
- const widgetY = maxY + 5;
- const widgetW = 150;
- const widgetH = 50;
+ // 텍스트 바로 아래에 스탬프(서명 이미지) 배치
+ const widgetX = minX;
+ const widgetY = maxY + 5;
+ const widgetW = 150;
+ const widgetH = 50;
- const buyerSignature = await getBuyerSignatureFileWithFallback(); // 기존 프로젝트 함수 그대로 사용
- if (!buyerSignature?.data?.dataUrl) {
- console.warn('⚠️ 구매자 서명 이미지가 없습니다.');
- return;
- }
+ const stamp = new Annotations.StampAnnotation();
+ stamp.PageNumber = pageNumber;
+ stamp.X = widgetX;
+ stamp.Y = widgetY;
+ stamp.Width = widgetW;
+ stamp.Height = widgetH;
+ await stamp.setImageData(buyerSignature.data.dataUrl);
- const stamp = new Annotations.StampAnnotation();
- stamp.PageNumber = pageNumber;
- stamp.X = widgetX;
- stamp.Y = widgetY;
- stamp.Width = widgetW;
- stamp.Height = widgetH;
- await stamp.setImageData(buyerSignature.data.dataUrl);
+ annotationManager.addAnnotation(stamp);
+ annotationManager.drawAnnotationsFromList([stamp]);
- annotationManager.addAnnotation(stamp);
- annotationManager.drawAnnotationsFromList([stamp]);
+ signatureCount++;
+ console.log(`✅ 페이지 ${pageNumber}의 "삼성중공업_서명란" 위치에 자동 서명 적용 완료 (${signatureCount}번째)`);
- console.log('✅ "삼성중공업_서명란" 위치에 자동 서명 적용 완료');
- return; // 첫 매치만 처리
+ // 다음 검색을 위해 인덱스 이동
+ searchIndex = startIndex + 1;
+ }
}
- console.warn('⚠️ 문서에서 "삼성중공업_서명란"을 찾지 못했습니다.');
+ if (signatureCount > 0) {
+ console.log(`✅ 총 ${signatureCount}개의 "삼성중공업_서명란" 위치에 자동 서명 적용 완료`);
+ } else {
+ console.warn('⚠️ 문서에서 "삼성중공업_서명란"을 찾지 못했습니다.');
+ }
} catch (error) {
console.error('구매자 자동 서명 처리 실패:', error);
}