코드에서 작성한 PDF 양식에서 Tab을 누르면 커서가 있어야 할 곳에서 두 필드 떨어진 곳에 위치하거나, 두 번째 열을 완전히 건너뛰거나, 네 번째 필드가 아닌 세 번째 필드 뒤에 다시 맨 위로 점프합니다. 뷰어에서 송장을 작성하는 사람은 키보드가 지금까지 사용했던 모든 웹 양식과 같은 방식으로 양식을 이동하기를 기대합니다. 그렇지 않으면 그들은 마우스로 다음 입력란을 찾으며, 여러분의 도구가 아직 미완성이라고 조용히 결론 내립니다. 예측 가능한 필드 이동은 사람들이 참아내는 데이터 입력 뷰어와 신뢰하는 뷰어 사이의 차이를 만들며, 이는 시뮬레이션된 클릭으로 키보드 입력을 위조하는 대신 올바른 포커스 API를 사용하는가의 문제에 전적으로 달려 있습니다
아래 예제는 Delphi, C++Builder 및 Lazarus를 위한 PDFium 기반 VCL/LCL 구성 요소인 PDFium Component를 사용합니다. 탐색은 양식 뷰어가 제대로 수행해야 하는 세 가지 중 하나입니다. 나머지 두 가지는 양식을 올바르게 열고 입력된 값이 실제로 표시되도록 저장하는 것인데, 대부분의 놀라운 문제들은 이 두 곳에 숨어 있으므로 세 가지 모두 아래에서 다룹니다
양식 열기: FormFill, FormType 및 XFA 문제
필드에 액세스하려면 문서를 열기 전에 FormFill 속성으로 제어되는 양식 채우기(form-fill) 하위 시스템이 활성화되어 있어야 합니다. 활성화되면 FormType은 어떤 종류의 양식을 다루고 있는지 알려주며, 그에 따라 제공할 수 있는 기능 세트가 달라집니다
Pdf.FileName := FormPath;
Pdf.FormFill := True; // enable before Active; required for any field access
Pdf.Active := True;
case Pdf.FormType of
ftNone:
DisableFormPanel('This document has no interactive form');
ftAcroForm:
BuildFieldList; // full field navigation and editing available
ftXfaFull:
ShowXfaNotice; // XFA renders from its own XML template;
// treat field editing as limited
end;
이 switch 문에서는 두 가지 실용적인 참고 사항이 따릅니다. AcroForm은 표준 ISO 32000 양식 모델이며, 여기서 모든 API가 대상으로 하는 것입니다. XFA 문서는 자체 XML 양식 아키텍처를 포함하므로 간단한 AcroForm 데모를 보여준 후 고객에게 완벽한 XFA 편집을 약속하는 것은 후회할 일이 될 것입니다. 두 번째 참고 사항은 부작용에 관한 것입니다. FormFill을 True로 설정하면 문서 JavaScript도 초기화됩니다. 누군가 입력할 때 누계가 현재 상태로 유지되도록 하는 것이 계산 스크립트이기 때문에 데이터 입력 뷰어에서는 이 동작이 정확히 맞습니다. 반면 출처를 알 수 없는 파일의 미리보기 창에서는 완전히 잘못된 것입니다. 안전한 PDF 미리보기 기사에서 이 장단점 중 FormFill := False 측면을 다룹니다
사용자가 기대하는 곳에 도달하는 Tab 키 이동
위에서 언급한 키보드 문제로 돌아가 보겠습니다. 다음 위젯의 사각형 영역에 시뮬레이션된 마우스 클릭을 발생시켜 Tab을 위조하려는 유혹이 생기겠지만, 이는 필드가 화면 밖으로 스크롤되거나 두 위젯이 겹치는 순간 망가지게 됩니다. 대신 포커스 API는 지오메트리를 추측할 필요 없이 양식 자체의 포커스를 직접 이동시킵니다. 다섯 가지 호출로 모든 것을 다룹니다. 인덱스를 통한 FocusFormField, 단계별 이동을 위한 FocusNextFormField 및 FocusPreviousFormField, 현재 위치를 읽어오기 위한 FocusedFormFieldIndex, 포커스를 완전히 해제하는 ClearFormFieldFocus입니다
procedure TFormViewer.HandleTabKey(Shift: TShiftState);
begin
if ssShift in Shift then
PdfView.FocusPreviousFormField
else
PdfView.FocusNextFormField;
UpdateFieldStatus; // e.g. "Field 4 of 17: InvoiceDate"
end;
사람들을 곤경에 빠뜨리는 동작 중 하나는 wrap(순환)입니다. 이동은 현재 페이지의 탭 순서를 통과하여 페이지 내에서 반복됩니다. 마지막 필드를 지나면 다시 첫 번째 필드로 돌아갑니다. 두 단계 이동 기능은 모두 새로운 필드 인덱스를 반환하거나, 페이지에 필드가 전혀 없을 때 -1을 반환합니다. 이러한 반복은 문서 단위가 아니라 페이지 단위이므로 다음 페이지로 넘어가는 것은 라이브러리가 아니라 여러분의 몫입니다. 반환된 인덱스와 시작 인덱스를 비교하고 언제 순환했는지 확인하여 양식이 하나의 연속적인 순서로 읽히도록 의도된 경우 PageNumber를 직접 진행시키십시오. 이 확인을 건너뛰면 두 페이지 양식이 조용히 1페이지에 커서를 가두어 버리며, 이는 Tab 고장 불만의 또 다른 형태입니다
UI의 나머지 부분이 반응할 때 이동 기능은 더욱 유용해집니다. 포커스가 도달하면 OnFormFieldEnter 이벤트가 발생하고 뷰어의 OnFormFieldFocusChange가 새 필드 인덱스를 보고하므로 측면 패널은 키보드가 방금 선택한 항목과 보조를 맞출 수 있습니다. 화면 위치에서 필드로의 역방향 매핑이 필요한 경우 FormFieldAt 인덱싱 속성이 툴팁 미리보기 및 클릭하여 편집(click-to-edit) 패널을 위한 적중 테스트(hit-testing)를 수행합니다. 이 모든 것에는 조용한 접근성 이점이 존재합니다. 포커스는 문서 고유의 필드 순서를 따르기 때문에 Tab 키를 위해 연결한 경로는 추가 작업 없이 스크린 리더가 안내하는 경로와 동일해집니다
단순 인덱스 번호 대신 필드 이름을 표시하려면 속성 하나가 더 필요합니다. FormFieldInfo[]는 각 인덱스마다 필드 이름, 유형, 글꼴 크기, 체크 상태, 내보내기 값, 그리고 탐색 목록에 표시되어야 할 그룹 멤버십이 포함된 TPdfFormFieldInfo 레코드를 반환합니다("4" 대신 "Field 4 of 17: InvoiceDate"). 라디오 그룹은 전용 테스트 파일을 만들 가치가 있는 사례입니다. 여러 위젯이 단일 필드 이름을 공유할 수 있으므로 위젯에서 순진하게 조합된 목록은 동일한 그룹을 여러 번 표시하여 읽는 사람을 혼란스럽게 만듭니다
채워진 값이 빈 상태로 나오는 이유와 이를 해결하는 호출
지원 대기열을 채우는 또 다른 불만은 오작동하는 Tab 키보다 더 우려스럽습니다. 양식이 프로그래밍 방식으로 채워지고 고객이 Acrobat에서 열면 모든 필드가 비어 있는 것처럼 보인다는 것입니다. 필드를 클릭하면 그제서야 값이 표시됩니다. 데이터는 항상 파일 안에 있습니다. 누락된 것은 데이터의 이미지(picture)이며, 이는 버그의 전체 유형을 설명하기 때문에 그 이유를 한 번 이해할 가치가 있습니다
AcroForm 텍스트 필드는 필드 딕셔너리의 /V 항목에 해당 값을 저장합니다(ISO 32000-1 §12.7.3.3). 뷰어가 실제로 그리는 것은 콘텐츠의 작은 사전 렌더링(pre-rendered) 스니펫인 /AP 아래 위젯의 외양 스트림(appearance stream)으로 완전히 별개입니다(§12.5.5). /V만 작성하고 /AP를 그대로 두면 이 둘은 따로 놀게 됩니다. 값은 있지만 렌더링된 버전은 오래되었거나 누락되어 있습니다. Acrobat은 포커스를 얻을 때 우연히 필드의 외양을 다시 작성하는데, 이것이 클릭할 때만 값이 나타나는 완벽한 이유입니다. 뷰어에게 외양을 다시 생성하도록 요청하는 예전 NeedAppearances 플래그는 일관되게 작동한 적이 없고 PDF 2.0에서 더 이상 사용되지 않으며, 인쇄 서버 및 썸네일 생성기는 이를 완전히 무시합니다. 이들은 /AP 외에는 아무것도 그리지 않으므로 /AP가 비어 있으면 빈 상자를 인쇄합니다
FormField[i]를 통해 값을 할당하면 /V만 작성됩니다. 이것이 양식 채우기가 3단계 시퀀스인 이유이며, 개발 팀이 누락하는 단계가 바로 그 중간 단계입니다
procedure TFormViewer.FillAndSave(const Values: array of WString;
const OutputPath: string);
var
i: Integer;
begin
for i := 0 to Pdf.FormFieldCount - 1 do
Pdf.FormField[i] := Values[i]; // writes /V only
// Rebuild the /AP appearance streams; without this the form
// looks blank in Acrobat until each field is clicked
Pdf.GenerateFormAppearances;
Pdf.SaveAs(OutputPath);
end;
해결책의 전부는 GenerateFormAppearances입니다. 이는 현재 값, 글꼴 및 정렬(quadding)을 사용하여 모든 위젯의 외양 스트림을 재구성하므로 포커스 이벤트를 절대 실행하지 않는 뷰어, 인쇄 서버 또는 썸네일 생성기도 채워진 상태를 그대로 그리게 됩니다. 필드 당 한 번씩 호출하지 말고 배치 작업 후에 한 번 호출하십시오. 외양 생성은 실제 레이아웃 작업을 수행하며, 필드별 호출은 의미 없이 대형 양식 전체에 걸쳐 부하를 배가시킵니다
외양을 재구성하는 것은 폰트와 정렬이 적용되는 순간이기도 하며, 이는 2차적인 당혹감의 원인이 됩니다. 새 스트림은 필드의 글꼴, 크기 및 정렬을 사용하여 각 값을 위젯 사각형 내에 배치합니다. 테스트 양식에 편안하게 자리 잡은 값도 동일한 필드가 더 좁은 고객의 사본에서는 잘리거나 축소될 수 있습니다. 자동 크기 필드(글꼴 크기 0)는 텍스트를 축소하여 맞춥니다. 고정 크기 필드는 잘라냅니다. 둘 다 합법적이며 해당 양식이 무엇을 수행하는지 알 수 있는 유일한 솔직한 방법은 작성한 문자열이 아닌 재구성된 출력을 살펴보는 것입니다. 누군가 상자 가장자리에서 텍스트가 잘렸다고 보고하면 거의 항상 이것이 이유입니다
검증을 사후 고려 사항이 아니라 작업을 완료하는 과정의 일부로 취급하십시오. Acrobat에서 저장된 파일을 열고 아무 필드도 건드리지 않은 상태에서 값이 보이는지 확인하십시오. 그런 다음 PDF 또는 다른 뷰어(양식 논리를 완전히 무시하는 뷰어)에서 이미지로 인쇄하고 이 경로에서도 값이 살아남는지 확인하십시오. 이 두 가지 검사를 통해 /V-대-/AP 드리프트의 모든 변종을 포착할 수 있습니다
데모는 통과하지만 현장에서는 실패하는 필드 구성
깔끔한 데모 양식에는 고객 파일에는 없는 일련의 예외 상황이 숨겨져 있습니다. 그중 네 가지는 "내 컴퓨터에서는 작동했습니다"라는 보고의 대부분을 차지합니다
- 확인란 내보내기 값. "on" 상태가 항상
Yes인 것은 아닙니다. 양식은 고유한 내보내기 값을 자유롭게 정의할 수 있으며, 잘못된 문자열을 쓰면 코드는 값을 설정했다고 확신하지만 시각적으로 상자는 체크 해제된 상태로 남습니다. 내보내기 값을 가정하지 말고FormFieldInfo[]에서 읽어 오십시오 - 공유 이름 라디오 그룹. 하나의 필드, 여러 개의 위젯. 할당하는 값에 따라 어떤 위젯이 선택된 것으로 읽힐지가 결정되므로 한 이름이 하나의 사각형에 매핑된다고 가정하는 UI 코드는 엉뚱한 버튼에 포커스 링을 그리게 됩니다
- 계산된 필드. 문서 JavaScript에 의해 유지 관리되는 합계는 필드 이벤트에 대한 응답으로 업데이트됩니다. 이러한 이벤트를 우회하는 프로그래밍 방식 채우기는 재계산을 트리거하거나 계산된 필드를 직접 덮어써야 합니다. 항목별 기재 사항과 합계가 일치하지 않는 양식은 두 가지 해결책보다 더 최악입니다
- 숨겨진 필수 필드. 조건부 양식은 여전히 필수로 플래그가 지정된 필드를 숨깁니다. 유효성 검사가 가시성을 존중할지 원본 필수 플래그를 존중할지 미리 결정한 다음, 지원 팀이 찾을 수 있는 어딘가에 해당 결정을 적어두십시오
여러분에게 피해를 주기 전에 알아두어야 할 가치 있는 차이점이 하나 있습니다. 외양을 생성하는 것은 병합(flattening)이 아니라는 것입니다. GenerateFormAppearances는 필드를 편집 가능한 상태로 유지하면서 모든 곳에서 값을 표시합니다. 병합은 외양을 정적인 페이지 콘텐츠에 굽고 상호 작용을 영구적으로 제거하므로 보관용 사본에는 적합하지만 다음 사람이 계속 작성해야 할 양식에는 잘못된 것입니다. FormType이 ftAcroForm이 아닌 ftXfaFull을 보고하는 경우 문서가 고유한 XML 템플릿에서 렌더링되므로 어차피 이 곳의 편집 영역은 어느 것도 깔끔하게 적용되지 않습니다. 사용자가 스스로 한계를 찾게 두는 대신 그 사례를 감지하고 사용자에게 알려주십시오
여기에 표시된 양식 채우기 하위 시스템, 포커스 이동 및 외양 생성은 Delphi, C++Builder 및 Lazarus/FPC용 PDFium Component의 일부입니다. 만일 뷰어가 양식 데이터와 함께 검토자 마크업도 처리한다면 주석 검토 기사에서 해당 인접 모델을 다룹니다