기술 문서

Delphi 및 PDFium에서의 저시력자용 PDF 색상 필터

저시력 독자는 기본 대비에서 흰색 배경의 검은색 텍스트를 알아볼 수 없으므로 다크 모드를 요구합니다. 가장 단순한 방법은 렌더링된 페이지의 모든 픽셀을 반전시키는 것입니다. 이는 일주일 만에 출시되지만 다음 날 바로 문제가 발생합니다. 스캔한 사진은 필름 음화처럼 보이고, 독자가 노란색 형광펜으로 표시한 부분은 읽을 수 없는 파란색 얼룩으로 변하며, 인쇄물이 왜 완전히 검게 나오는지 묻는 사람도 생깁니다. 이 기능은 구축할 가치가 충분하며 절반 정도만 맞게 구현하기도 매우 쉽습니다. 이 두 결과 사이의 차이는 한 가지 아이디어에 있습니다. 각 색상 결정은 렌더링 파이프라인의 특정 단계에 속하며, 반전은 잘못된 단계에 적용된 잘못된 도구라는 점입니다. 여기에 있는 코드는 렌더링 API가 이러한 단계들을 개별적으로 노출하는 Delphi, C++Builder 및 Lazarus용 PDFium 기반 뷰어인 PDFium Component를 사용합니다

필터는 문서 상태가 아닌 프레젠테이션 상태입니다

여기서 한 가지 규칙이 최악의 버그를 방지합니다. 읽기 모드는 비트맵이 생성되거나 후처리되는 방식만 변경할 뿐 그 외에는 아무것도 변경하지 않습니다. PDF 바이트는 그대로 유지되고, 모든 모드는 다시 렌더링하여 되돌릴 수 있으며, "저장"은 필터링된 모양을 파일에 다시 기록하지 않습니다. 이는 명백하게 들리지만, 법무 검토자가 활성화된 필터 아래에서 계약서를 인쇄하고 반전된 버전을 제출할 때까지는 그렇습니다. 그 시점에서 "인쇄 시 문서 자체의 모양을 사용하는가 아니면 화면의 모양을 사용하는가"라는 질문은 코드 경로의 우연이 아니라 사양에 명시적인 답변이 필요하다는 것이 밝혀집니다. 필터 설정은 뷰어 상태로 유지하고 렌더링 시에 적용하며, 모든 내보내기 경로가 어떤 모양을 사용할지 선언하도록 하십시오

이 규칙은 두 배의 가치를 지닙니다. 모드를 전환하면 변경되지 않은 소스에서 다시 렌더링하기 때문에 가역성은 무료로 얻을 수 있습니다. 유지할 실행 취소 스택이 없으며 모드 변경을 연속으로 실행하더라도 페이지가 손상될 방법이 없습니다. 다중 창 시나리오도 같은 이유로 일관성을 유지합니다. 문서 개체는 공유된 상태로 유지되면서 각 뷰가 고유한 프레젠테이션 상태를 가지므로 한 문서의 두 뷰에서 서로 다른 모드를 실행할 수 있습니다

먼저 렌더링한 다음, 변환하십시오

지원되는 패턴은 렌더링 후 비트맵 처리입니다. RenderPage가 페이지 래스터를 생성한 다음 변환 단계가 이를 조정합니다. 이 컴포넌트는 제자리 비트맵 작업으로 InvertPdfBitmap, DuotonePdfBitmap, GrayscalePdfBitmap의 세 가지 변환을 제공하며, 이를 통해 모드 전환을 깔끔한 2단계 함수로 만듭니다

function TViewerForm.RenderWithMode(W, H: Integer): TBitmap;
begin
  Result := Pdf.RenderPage(0, 0, W, H, ro0, [reAnnotations]);
  case FReadingMode of
    rmInverted:     InvertPdfBitmap(Result);
    rmHighContrast: DuotonePdfBitmap(Result, clBlack, $0000C8FF);  // dark bg, amber text
    rmGrayscale:    GrayscalePdfBitmap(Result);
  end;
  // rmNormal falls through: the document keeps its own colors
end;

이 설계에서는 두 가지가 뒤따릅니다. 첫째, 변환 비용은 비트맵 크기에 비례하므로 렌더링 결과가 캐시되는 곳에서 이 작업이 수행되어야 합니다. 매번 그릴 때마다가 아니라 캐시된 비트맵을 한 번만 필터링하십시오. 둘째, 변환이 완성된 래스터에서 실행되기 때문에 텍스트, 벡터 아트, 이미지, 주석 모양에 동일하게 적용됩니다. 이러한 균일성이 바로 단순 반전이 사진에서 실패하는 이유입니다. 이것이 듀오톤 변환이 텍스트가 많은 문서에 더 나은 기본값이 되는 이유인데, 색조를 부정하는 대신 광도를 선택한 어두운색에서 밝은색으로의 색상 램프에 매핑하기 때문입니다. 반전은 원하는 독자를 위한 명시적인 선택 사항으로 계속 사용할 수 있습니다. 더 선명한 글리프 가장자리는 별도의 수단입니다. reNoSmoothText 렌더링 옵션은 렌더링 시 텍스트 안티앨리어싱을 끄며, 큰 확대/축소 배율에서 고대비 모드와 잘 어울립니다

서로 다른 결과를 내는 두 가지 그레이스케일

렌더링 옵션에는 reGrayscale이 포함되어 있는데, 이는 후처리 단계를 건너뛰는 지름길처럼 보입니다. 하지만 동일한 작업이 아닙니다

// Engine-level: grayscale applied during rasterization
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);

// Post-process: render in color, convert the finished bitmap
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);

엔진 수준 옵션은 이미지 콘텐츠의 래스터 출력에 적용되지만 벡터 채우기나 텍스트 색상에는 미치지 않으므로 색상이 있는 제목이 있는 페이지는 사진이 회색으로 변해도 제목은 여전히 파란색으로 남을 수 있습니다. 완성된 비트맵에 대한 GrayscalePdfBitmap은 조건 없이 모든 것을 변환합니다. 일부 저시력 독자가 특별히 선호하는 텍스트 색상은 신호로 유지하면서 이미지를 채도 감소시키려는 경우 렌더링 옵션은 여전히 유용합니다. 그러나 요구 사항이 "그레이스케일 페이지"라면 후처리가 이를 충족하는 버전입니다. 어느 경로를 선택하든 두 가지 RenderPage 오버로드 스타일을 모두 염두에 두십시오. 함수 형태는 호출자가 소유하고 해제해야 하는 비트맵을 반환하며, 이는 필터가 진행 중인 렌더링된 비트맵 수를 배로 늘리자마자 중요해집니다

배경, 선택 표시 및 PageColor의 함정

모든 편안함 조정이 변환인 것은 아닙니다. 흰색 페이지 배경을 따뜻한 톤으로 바꾸는 것만으로도 눈부심에 민감한 독자에게는 충분한 경우가 많으며, 여기에는 전용 속성이 있습니다. 이 속성에는 사람들을 곤경에 빠뜨리는 범위 규칙이 있습니다

// Affects the on-screen view only
PdfView.PageColor := $00D9EDF2;  // warm paper tone behind page content

// RenderPage output ignores PageColor; pass the color explicitly
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);

PageColorTPdfView가 표시하는 내용을 변경하지만 RenderPage를 통해 생성된 비트맵은 Color 매개변수에서 달리 지정하지 않는 한 기본 흰색을 유지합니다. 증상은 확실합니다. 화면에는 색이 칠해진 페이지가 표시되지만 사용자가 내보내거나 인쇄하면 출력이 흰색으로 되돌아갑니다. 이를 첫 번째 섹션의 내보내기 정책 결정과 동일하게 분류하십시오

나머지 색상 속성은 오버레이 표시를 정의합니다. 검색 적중에 대한 HighlightColor, 사용자 텍스트 선택에 대한 SelectionColor, 음성 단어 커서에 대한 ReadingWordColor입니다. 제공하는 모든 필터 아래에서 이들 각각을 다시 확인해야 합니다. 흰색 배경에서 잘 작동하던 호박색 읽기 커서는 반전 후 사라지고, 옅은 파란색 선택 영역은 고대비 배경에 묻혀 보이지 않게 됩니다. 하나의 전역 설정 대신 모드별 오버레이 팔레트를 유지 관리하고 의도적으로 조합을 테스트하십시오. 필터와 텍스트 음성 변환(TTS)의 조합은 이 기능이 제공되는 독자들에게 엣지 케이스가 아니라 일반적인 구성입니다. 오버레이 메커니즘 자체는 접근성 있는 리더 기사에서 다룹니다

수치, 검증 및 인쇄 문제

WCAG 2.1은 이 기능을 측정할 수 있는 것으로 만듭니다. 성공 기준 1.4.3은 본문 텍스트에 대해 4.5:1의 명암비를 요구하고, 1.4.6은 향상된 명암비를 위해 이를 7:1로 높입니다. 실제 렌더링된 출력에서 실행되는 명암비 분석기를 사용하여 이러한 비율에 대해 고대비 모드를 무작위로 확인하십시오. 이미지 위의 텍스트나 양식 필드의 텍스트는 본문 텍스트가 기준을 통과하더라도 조용히 실패하는 곳입니다

인쇄는 자체적인 결정이 필요하며 방어 가능한 기본값은 문서 고유의 모양을 사용하는 것이고, "표시된 대로 인쇄"는 명시적인 사용자 선택으로 제공하는 것입니다. 인쇄된 페이지는 뷰어 개발자가 예상하는 것보다 더 많은 워크플로에서 증거로 사용되며, 반전된 계약서 인쇄물은 법적인 문제의 소지가 있는 지원 사례가 됩니다. 성능에 중요한 또 다른 조합이 있습니다. 필터링된 렌더링은 매 모드 전환 시 비트맵 작업을 두 배로 늘리므로 각 페인트 메시지에 변환을 적용하지 마십시오. 필터링된 비트맵을 캐시하고 페이지, 확대/축소 또는 모드가 실제로 변경될 때만 변환을 다시 실행하십시오. 이를 저렴하게 만드는 캐싱 전략은 렌더 캐시 및 줌 성능 기사에 있습니다

코드가 아닌 UI에서 해결해야 할 한 가지는 어떤 모드가 올바른 기본값인가 하는 점입니다. 단일한 정답은 없으므로 세트를 제공하고 독자가 선택하도록 하십시오. 고대비는 텍스트가 많은 대부분의 읽기에 적합하고, 반전은 어두운 바탕에 밝은 글씨를 특별히 원하는 독자에게 맞으며, 그레이스케일은 색상 노이즈를 줄여주고, 배경 틴트는 눈부심 민감도를 처리합니다. 사용자별로 선택을 유지하고 시작 시 이를 복원하며, 읽을 수 없는 모드에 빠진 독자에게는 빠른 탈출 방법이 필요하므로 단일 키 입력으로 정상으로 돌아갈 수 있는 경로를 유지하십시오

여기에 사용된 렌더링 옵션, 비트맵 변환 및 뷰 색상 속성은 Delphi, C++Builder 및 Lazarus/FPC용 PDFium Component와 함께 제공되며, 변환 구현을 감사하거나 확장할 수 있도록 전체 소스가 포함되어 있습니다