مقاله فنی

فیلترهای رنگی PDF برای کم‌بینایان در دلفی با PDFium

یک خواننده کم‌بینا نمی‌تواند متن سیاه را در صفحه سفید با کنتراست پیش‌فرض تشخیص دهد، بنابراین آنها حالت تاریک را درخواست می‌کنند. پاسخ ساده‌لوحانه این است که هر پیکسل از صفحه رندر شده را وارونه کنیم. این در یک هفته عرضه می‌شود و روز بعد خراب می‌شود: عکس‌های اسکن شده شبیه نگاتیو فیلم برمی‌گردند، علامت‌های هایلایتر زرد خواننده به یک لکه آبی ناخوانا تبدیل می‌شوند، و کسی می‌پرسد چرا پرینت کاملاً سیاه چاپ شده است. این ویژگی واقعاً ارزش ساختن را دارد و واقعاً به راحتی می‌توان آن را نیمه‌کاره انجام داد، و فاصله بین این دو نتیجه یک ایده است: هر تصمیم رنگی متعلق به نقطه خاصی در خط لوله (pipeline) رندر است، و وارونگی ابزار اشتباهی است که در مرحله اشتباه به کار برده شده است. کد اینجا از PDFium Component استفاده می‌کند، نمایشگر مبتنی بر PDFium برای دلفی، C++Builder، و لازاروس، که API رندرینگ آن، این مراحل را به صورت جداگانه در معرض نمایش قرار می‌دهد

فیلترها وضعیت ارائه (presentation state) هستند، هرگز وضعیت سند نیستند

یک قانون در اینجا از بدترین دسته باگ‌ها جلوگیری می‌کند: حالت خواندن فقط نحوه تولید یا پس‌پردازشِ (post-processed) بیت‌مپ را تغییر می‌دهد، و نه هیچ چیز دیگر. بایت‌های PDF دست‌نخورده باقی می‌مانند، هر حالت با رندر مجدد قابل بازگشت است، و "ذخیره" هرگز یک ظاهر فیلتر شده را به فایل برنمی‌گرداند. این موضوع بدیهی به نظر می‌رسد تا زمانی که یک بازبین حقوقی قراردادی را تحت یک فیلتر فعال چاپ کند و نسخه وارونه را بایگانی کند. در آن نقطه، این سؤال که "آیا چاپ از ظاهر خود سند استفاده می‌کند یا صفحه نمایش" سزاوار یک پاسخ صریح در مشخصات (spec) شما است، نه یک تصادف در مسیر کد. تنظیم فیلتر را در وضعیت نمایشگر (viewer state) نگه دارید، آن را در زمان رندر اعمال کنید، و کاری کنید که هر مسیر استخراج (export path) اعلام کند از کدام ظاهر استفاده می‌کند

این قانون دو بار هزینه خود را پرداخت می‌کند. برگشت‌پذیری (Reversibility) رایگان به دست می‌آید، زیرا تغییر حالت‌ها از منبع تغییر نیافته دوباره رندر می‌شوند: هیچ پشته undo ای برای نگهداری وجود ندارد و هیچ راهی نیست که یک سری تغییر حالت بتواند صفحه را تنزل دهد. سناریوهای چند-پنجره‌ای نیز به همین دلیل منسجم باقی می‌مانند. دو نمای یک سند می‌توانند حالت‌های متفاوتی را اجرا کنند، زیرا هر نما وضعیت ارائه خود را دارد در حالی که شیء سند به صورت مشترک باقی می‌ماند

ابتدا رندر کنید، سپس تبدیل (transform) کنید

الگوی پشتیبانی شده پردازش بیت‌مپ پس از رندر است: RenderPage رسترِ (raster) صفحه را تولید می‌کند، سپس یک گذر تبدیل (transform pass) آن را تنظیم می‌کند. این کامپوننت دارای سه تبدیل به عنوان عملیات‌های درجای بیت‌مپ (in-place bitmap operations) است، InvertPdfBitmap، DuotonePdfBitmap، و GrayscalePdfBitmap، که باعث می‌شود تغییر حالت، یک تابع تمیز دو-مرحله‌ای باشد:

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;

دو چیز از این طراحی ناشی می‌شود. اول، هزینه تبدیل متناسب با اندازه بیت‌مپ است، بنابراین این کار متعلق به هر جایی است که نتایج رندر شما کش (cached) می‌شوند: بیت‌مپ کش شده را یک بار فیلتر کنید، نه در هر بار رسم شدن (paint). دوم، از آنجایی که تبدیل روی رسترِ تمام شده اجرا می‌شود، متن، هنر برداری، تصاویر، و ظواهر حاشیه‌نویسی را به یک شکل تحت تأثیر قرار می‌دهد. این یکنواختی دقیقاً همان چیزی است که وارونگیِ ساده برای عکس‌ها اشتباه انجام می‌دهد. به همین دلیل است که تبدیل دو-رنگ (duotone) پیش‌فرض بهتری برای اسناد پر-متن ایجاد می‌کند، زیرا روشنایی را روی یک رمپ رنگی انتخابی تیره-به-روشن پیاده می‌کند به جای اینکه طیف‌ها (hues) را خنثی کند؛ وارونگی به عنوان یک انتخاب صریح برای خوانندگانی که آن را می‌خواهند در دسترس باقی می‌ماند. لبه‌های واضح‌تر گلیف (glyph)، یک اهرم جداگانه هستند. گزینه رندر reNoSmoothText حالت anti-aliasing متن را در زمان رندر خاموش می‌کند و به خوبی با حالت کنتراست-بالا در زوم‌های بزرگ جفت می‌شود

دو مقیاس خاکستری (grayscale) که با هم توافق ندارند

گزینه‌های رندر شامل reGrayscale می‌شوند، که شبیه یک میانبر از کنار مرحله پس‌پردازش (post-processing) است. این همان عملیات نیست:

// 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);

گزینه سطح-موتور برای خروجی رستر از محتوای تصویر اعمال می‌شود اما به پرکننده‌های برداری (vector fills) یا رنگ‌های متن نمی‌رسد، بنابراین صفحه‌ای با سرفصل‌های رنگی می‌تواند با عکس‌های خاکستری و سرفصل‌هایی که سرسختانه آبی هستند برگردد. GrayscalePdfBitmap روی بیت‌مپِ تمام شده همه چیز را بی‌قیدوشرط تبدیل می‌کند. گزینه رندر هنوز هم جایگاه خود را زمانی به دست می‌آورد که بخواهید تصاویر در حالی که رنگ متن به عنوان یک سیگنال حفظ می‌شود، رنگ‌زدایی (desaturated) شوند، چیزی که برخی از خوانندگان کم‌بینا به طور خاص ترجیح می‌دهند. اما اگر نیازمندی، "صفحه خاکستری" خوانده می‌شود، پس‌پردازش نسخه‌ای است که آن را برآورده می‌کند. هر مسیری را که انتخاب می‌کنید، هر دو سبک سربارگذاری (overload) RenderPage را در نظر داشته باشید. فرمِ تابعی (function form) بیت‌مپی را برمی‌گرداند که متعلق به فراخواننده است و باید آن را آزاد کند، و این به محض اینکه فیلترها تعداد بیت‌مپ‌های رندر شده در حال پرواز (in flight) را چند برابر کنند، اهمیت پیدا می‌کند

پس‌زمینه‌ها، نشانه‌های انتخاب، و تله PageColor

هر تنظیمِ راحتی، یک تبدیل (transform) نیست. جایگزینی پس‌زمینه سفید صفحه با یک تُن گرم اغلب به تنهایی برای خوانندگان حساس به تابش خیره‌کننده (glare-sensitive) کافی است، و ویژگی اختصاصی خود را دارد. این ویژگی دارای یک قانون محدوده (scope rule) است که افراد را غافلگیر می‌کند:

// 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);

PageColor آنچه را که TPdfView نمایش می‌دهد تغییر می‌دهد، اما بیت‌مپ‌های تولید شده از طریق RenderPage سفیدی پیش‌فرض را حفظ می‌کنند مگر اینکه پارامتر Color چیز دیگری بگوید. این علامت قابل‌اعتماد است: صفحه نمایش، صفحه ته‌رنگ دار (tinted page) را نشان می‌دهد، کاربر خروجی (export) یا چاپ می‌گیرد، و خروجی به رنگ سفید برمی‌گردد. آن را زیرِ همان تصمیم خط‌مشی-استخراج از بخش اول بایگانی کنید

بقیه ویژگی‌های رنگی، نشانه‌های پوششی (overlay marks) را تعریف می‌کنند: HighlightColor برای یافت شده‌های جستجو، SelectionColor برای انتخاب متن توسط کاربر، ReadingWordColor برای مکان‌نمای کلمه خوانده شده (spoken-word cursor). هر یک از آن‌ها باید تحت هر فیلتری که ارائه می‌دهید دوباره بررسی شوند. یک مکان‌نمای خواندن کهربایی (amber) که روی رنگ سفید کار می‌کند، پس از وارونگی ناپدید می‌شود؛ یک انتخاب آبی کمرنگ (pale blue) در یک پس‌زمینه با کنتراست بالا محو می‌شود. پالت‌های پوششیِ هر-حالت را به جای یک مجموعه سراسری واحد حفظ کنید، و ترکیب‌ها را به صورت عمدی تست کنید. فیلترها به علاوه تبدیل متن به گفتار، یک پیکربندی عادی برای خوانندگانی است که این ویژگی به آنها خدمت می‌کند، نه یک موردِ حاشیه‌ای. خود ماشین‌آلات پوششی (overlay machinery) در مقاله خواننده دسترسی‌پذیر پوشش داده شده است

اعداد، تأییدیه، و مسئله چاپ

WCAG 2.1 این ویژگی را به چیزی تبدیل می‌کند که می‌توانید اندازه‌گیری کنید. معیار موفقیت 1.4.3 خواستار نسبت کنتراست 4.5:1 برای متن بدنه است، و 1.4.6 آن را به 7:1 برای کنتراستِ افزایش یافته بالا می‌برد. حالت کنتراست-بالای خود را در برابر این نسبت‌ها با یک تحلیل‌گر کنتراست که روی خروجی رندر شده واقعی اجرا می‌شود، به‌طور تصادفی بررسی کنید. متن روی تصاویر و متن در فیلدهای فرم جایی هستند که این نسبت‌ها حتی زمانی که متن بدنه قبول می‌شود، بی‌صدا شکست می‌خورند

چاپ سزاوار تصمیم‌گیری خاص خود است، و پیش‌فرض قابل‌دفاع، ظاهر خود سند است، با این شرط که "چاپ به همان صورتی که نمایش داده شده" به عنوان یک انتخاب صریح کاربر ارائه شود. یک صفحه چاپ شده در گردش‌کارهای بیشتری نسبت به آنچه که سازندگان نمایشگر انتظار دارند، یک مدرک محسوب می‌شود، و یک پرینتِ وارونه از یک قرارداد یک حادثه پشتیبانی با چاشنی حقوقی است. یک جفت‌سازی دیگر برای عملکرد مهم است: رندرینگ فیلتر شده کار بیت‌مپ را در هر تغییر حالت دو برابر می‌کند، بنابراین یک تبدیل (transform) را روی هر پیام رسم (paint) اعمال نکنید. بیت‌مپ فیلتر شده را کش (cache) کنید و تبدیل را فقط زمانی دوباره اجرا کنید که صفحه، زوم، یا حالت واقعاً تغییر کند. استراتژی کش کردنی که این کار را ارزان می‌کند در مقاله کش رندر و عملکرد زوم قرار دارد

یک چیز که باید به جای کد شما در UI شما حل‌وفصل شود این است: کدام حالت پیش‌فرضِ مناسب است. یک پاسخ واحد وجود ندارد، بنابراین مجموعه را ارائه دهید و اجازه دهید خواننده انتخاب کند. کنتراست بالا برای اکثر مطالعه‌های پر-متن مناسب است، وارونگی مناسبِ خوانندگانی است که به طور خاص روشن-روی-تاریک را می‌خواهند، مقیاس خاکستری نویز رنگ را کاهش می‌دهد، و یک ته‌رنگ پس‌زمینه حساسیت به تابش خیره‌کننده را کنترل می‌کند. این انتخاب را به ازای هر کاربر پابرجا نگه دارید (persist)، آن را در زمان راه‌اندازی بازیابی کنید، و یک مسیر یک-کلیدی (one-keystroke) برای بازگشت به حالت عادی حفظ کنید، زیرا خواننده‌ای که در حالتی فرود می‌آید که نمی‌تواند آن را بخواند، به یک راه خروج سریع نیاز دارد

گزینه‌های رندر، تبدیل‌های بیت‌مپ و ویژگی‌های رنگِ نمای مورد استفاده در اینجا به همراه PDFium Component برای دلفی، C++Builder و لازاروس/FPC، با سورس کامل عرضه می‌شوند تا پیاده‌سازی‌های تبدیل قابل ممیزی یا توسعه باشند