Читач зі слабким зором не може розрізнити чорний текст на білій сторінці за контрастністю за замовчуванням, тож просить темний режим. Наївна відповідь — інвертувати кожен піксель відрендереної сторінки. Це відвантажується за тиждень і ламається наступного дня: скановані фотографії повертаються схожими на кіноплівкові негативи, жовті позначки маркера читача перетворюються на нечитабельну синю пляму, а хтось запитує, чому роздрук вийшов суцільно чорним. Ця функція справді варта розробки й справді легко реалізується лише наполовину правильно, і розрив між цими двома результатами — одна ідея: кожне рішення про колір належить конкретній точці в конвеєрі рендеру, а інверсія — це не той інструмент, застосований не на тому етапі. Код тут використовує PDFium Component, переглядач на основі PDFium для Delphi, C++Builder і Lazarus, чий API рендеру розкриває ці етапи окремо
Фільтри — це стан подання, а не стан документа
Одне правило запобігає найгіршій категорії багів тут: режим читання змінює лише те, як виробляється чи постобробляється бітмап, і нічого більше. Байти PDF залишаються недоторканими, кожен режим оборотний через повторний рендер, а «зберегти» ніколи не записує відфільтрований вигляд назад у файл. Це звучить очевидно, поки юридичний рецензент не роздрукує контракт під активним фільтром і не подасть інвертовану версію. У цей момент питання «чи використовує друк власний вигляд документа, чи екранний» виявляється таким, що заслуговує на явну відповідь у вашій специфікації, а не на випадковість шляху коду. Тримайте налаштування фільтра в стані переглядача, застосовуйте його під час рендеру, і змусьте кожен шлях експорту оголошувати, який вигляд він використовує
Це правило окупається двічі. Оборотність дістається безкоштовно, бо перемикання режимів повторно рендерить з незмінного джерела: немає жодного стека скасувань, який треба підтримувати, і немає способу, щоб серія змін режиму погіршила сторінку. З тієї самої причини сценарії з кількома вікнами лишаються узгодженими. Два перегляди одного документа можуть працювати в різних режимах, оскільки кожен перегляд володіє власним станом подання, поки об'єкт документа лишається спільним
Спершу рендер, потім перетворення
Підтримуваний шаблон — обробка бітмапа після рендеру: RenderPage виробляє растр сторінки, а потім прохід перетворення його коригує. Компонент постачає три перетворення як операції над бітмапом на місці, 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); // темне тло, бурштиновий текст
rmGrayscale: GrayscalePdfBitmap(Result);
end;
// rmNormal провалюється далі: документ зберігає власні кольори
end;
З цього дизайну випливають дві речі. По-перше, вартість перетворення пропорційна розміру бітмапа, тож ця робота належить туди, де кешуються ваші результати рендеру: фільтруйте кешований бітмап один раз, а не при кожному малюванні. По-друге, оскільки перетворення виконується на готовому растрі, воно однаково зачіпає текст, векторну графіку, зображення й вигляд анотацій. Саме ця однорідність — те, що звичайна інверсія робить неправильно для фотографій. Це причина, чому дуотонове перетворення стає кращим варіантом за замовчуванням для текстово-насичених документів, оскільки воно відображає яскравість на обрану темно-світлу колірну рампу замість заперечення відтінків; інверсія лишається доступною як явний вибір для читачів, які цього хочуть. Різкіші краї гліфів — окремий важіль. Опція рендеру reNoSmoothText вимикає згладжування тексту під час рендеру й добре поєднується з режимом високого контрасту при великому масштабі
Дві відтінки сірого, які не збігаються
Опції рендеру включають reGrayscale, що виглядає як обхідний шлях повз крок постобробки. Це не та сама операція:
// На рівні рушія: відтінки сірого застосовуються під час растеризації
GrayA := Pdf.RenderPage(0, 0, W, H, ro0, [reGrayscale]);
// Постобробка: рендер у кольорі, перетворення готового бітмапа
GrayB := Pdf.RenderPage(0, 0, W, H);
GrayscalePdfBitmap(GrayB);
Опція на рівні рушія застосовується до растрового виводу вмісту зображень, але не сягає векторних заливок чи кольорів тексту, тож сторінка з кольоровими заголовками може повернутися із сірими фотографіями та вперто синіми заголовками. GrayscalePdfBitmap на готовому бітмапі перетворює все, безумовно. Опція рендеру все ж заслуговує на своє місце, коли ви хочете знебарвити зображення, зберігаючи колір тексту як сигнал, що деякі читачі зі слабким зором прямо надають перевагу. Але якщо вимога звучить як «сторінка у відтінках сірого», саме постобробка є версією, яка її задовольняє. Який би шлях ви не обрали, тримайте в голові обидва стилі перевантаження RenderPage. Функціональна форма повертає бітмап, яким володіє і який повинен звільнити викликач, і це має значення, щойно фільтри помножують кількість відрендерених бітмапів у польоті
Фони, позначки виділення та пастка PageColor
Не кожне налаштування комфорту — це перетворення. Заміна білого фону сторінки на теплий відтінок часто сама по собі достатня для читачів, чутливих до відблисків, і для цього є спеціальна властивість. Ця властивість несе правило області дії, яке ловить людей:
// Впливає лише на екранний перегляд
PdfView.PageColor := $00D9EDF2; // теплий відтінок паперу за вмістом сторінки
// Вивід RenderPage ігнорує PageColor; передавайте колір явно
Bmp := Pdf.RenderPage(0, 0, W, H, ro0, [], $00D9EDF2);
PageColor змінює те, що показує TPdfView, але бітмапи, вироблені через RenderPage, зберігають типовий білий колір, якщо параметр Color не каже інакше. Симптом надійний: екран показує тонований колір сторінки, користувач експортує чи друкує, і вивід повертається до білого. Занесіть це до того самого рішення про політику експорту з першого розділу
Решта властивостей кольору визначають накладні позначки: HighlightColor для збігів пошуку, SelectionColor для виділення тексту користувачем, ReadingWordColor для курсора озвученого слова. Кожну з них потрібно перевіряти заново під кожним фільтром, який ви пропонуєте. Бурштиновий курсор читання, що працює на білому, зникає після інверсії; блідо-синє виділення розчиняється в тлі високого контрасту. Підтримуйте окремі накладні палітри для кожного режиму замість одного глобального набору й навмисно тестуйте комбінації. Фільтри разом із синтезом мовлення — це нормальна конфігурація для читачів, яким служить ця функція, а не крайовий випадок. Сама машинерія накладних позначок розглядається у статті про доступний читач
Цифри, перевірка та питання друку
WCAG 2.1 перетворює цю функцію на щось вимірюване. Критерій успіху 1.4.3 вимагає коефіцієнт контрастності 4.5:1 для основного тексту, а 1.4.6 підіймає його до 7:1 для посиленого контрасту. Вибірково перевіряйте свій режим високого контрасту за цими коефіцієнтами за допомогою аналізатора контрасту, запущеного на реальному відрендереному виводі. Текст поверх зображень і текст у полях форм — це місця, де коефіцієнти тихо провалюються, навіть коли основний текст проходить
Друк заслуговує на власне рішення, і виправданий варіант за замовчуванням — власний вигляд документа, з опцією «друкувати як відображається» як явним вибором користувача. Роздрукована сторінка — це доказ у більшій кількості робочих процесів, ніж автори переглядачів схильні очікувати, і інвертований роздрук контракту — це інцидент підтримки з юридичним присмаком. Ще одне поєднання важливе для продуктивності: відфільтрований рендер подвоює роботу з бітмапом при кожному перемиканні режиму, тож не застосовуйте перетворення при кожному повідомленні малювання. Кешуйте відфільтрований бітмап і повторно запускайте перетворення лише тоді, коли сторінка, масштаб чи режим справді змінюються. Стратегія кешування, яка робить це дешевим, живе у статті про кеш рендеру та продуктивність масштабування
Одну річ варто вирішити у вашому UI, а не в коді: який режим має бути правильним за замовчуванням. Єдиної відповіді немає, тож пропонуйте весь набір і дозвольте читачеві обрати. Високий контраст підходить для більшості текстово-насиченого читання, інверсія підходить читачам, які саме хочуть світле-на-темному, відтінки сірого прибирають кольоровий шум, а тонування фону вирішує чутливість до відблисків. Зберігайте вибір для кожного користувача, відновлюйте його при запуску й тримайте шлях в один натиск клавіші назад до звичайного режиму, оскільки читачеві, який потрапив у режим, який не може прочитати, потрібен швидкий вихід
Опції рендеру, перетворення бітмапів і властивості кольору перегляду, використані тут, постачаються разом із PDFium Component для Delphi, C++Builder і Lazarus/FPC, з повним вихідним кодом, щоб реалізації перетворень можна було перевірити чи розширити