Админ

четверг, 18 декабря 2014 г.

Генератор QR-кодов на Delphi / Lazarus: DelphiZXingQRCodeEx

|
Некоторое время назад мне пришлось решать такую задачу: нужно было добавить в документ Microsoft Office картинку с двумерным штрих-кодом (QR-кодом). Изучение вопроса показало, что большинство готовых решений либо требуют денег (в общем случае это не проблема, но в госучреждениях необходимо организовывать тендер, что может тянуться месяцами), либо требуют пересылки данных через Интернет (что при наших объёмах в тысячи документов слишком медленно и ненадёжно), либо «тянут» за собой внешние DLL-библиотеки, что просто неудобно.


Наиболее подходящим решением мне показался набор классов DelphiZXingQRCode с открытыми исходными кодами, который позволяет добавить нужный функционал в exe-файл безо всяких внешних связей. Уже в процессе работы выяснилось, что решение не идеально: во-первых, код содержит ошибки, во-вторых, разумеется, не поддерживает кодировку Win-1251, которую рекомендуют использовать ГОСТ Р 56042-2014 и Сбербанк РФ (из-за него всё и началось, см. ниже параграф Применение). Ошибки я исправил, поддержку разных не предусмотренных международным стандартом (но рекомендуемых нашим государственным) кодировок добавил. Поскольку авторы оригинального кода определённо не нуждаются в этих исправлениях, а лицензия Apache 2.0 вполне допускает создание проектов с открытым исходным кодом на базе этого, я так и поступил. Исправленный проект называется без изысков: DelphiZXingQRCodeEx.

Как пользоваться

По ссылкам ниже помимо исходных кодов имеется демонстрационный пример — приложение, которое реализует большинство возможностей класса. Я также включил exe-файл, чтобы можно было  оценить все эти возможности, не теряя времени на компиляцию.

Ссылки на скачивание

  • GitHub (здесь всегда последняя версия),
  • Google Drive (21.01.2016, ZIP, 353 Кб) — на случай если GitHub снова заблокируют на территории РФ (и для тех, кто не любит или не умеет им пользоваться),
  • Dropbox (21.01.2016, ZIP, 353 Кб) — на случай если предыдущие две ссылки не работают или чем-то смущают.

  • Отдельно ссылка на программу для генерации QR-кодов (21.01.2016, EXE, 240 Кб) — чтобы посмотреть, как это работает (технические подробности см. в следующем параграфе, пункт 6). Она также содержится во всех перечисленных выше источниках.

Изменения по сравнению с проектом DelphiZXingQRCode

Подробный список всех изменений можно посмотреть в архиве в файле CHANGELOG-RUS.md (читать этот файл онлайн на GitHub). Если очень кратко, то:
  1. Добавлена возможность определения собственных способов кодирования входных строк.
Это, пожалуй, самое главное отличие этого форка от оригинала. Всё остальное — либо восстановление функционала (см. пункт 2), либо исправление мелких ошибок, либо «косметические» доработки. Программисту теперь доступен класс TEncoder, от которого он может производить потомков. Класс-потомок может быть связан с целочисленной константой — идентификатором кодировки, и тогда его метод Encode будет использован для обработки входных данных.

В качестве примера реализованы кодирование Win-1251 и кодирование URL (преобразование нелатинских символов в %-коды, например, https://ru.wikipedia.org/wiki/QR-кодhttps://ru.wikipedia.org/wiki/QR-%D0%BA%D0%BE%D0%B4).
  1. Реализован выбор уровня коррекции ошибок.
Вообще-то, он был в оригинальной библиотеке ZXing (см. ссылки в конце статьи), но при портировании на Delphi его «сломали». Странно, что авторы за год так и не исправили эту ошибку сами, хотя она им точно известна (есть в разделе Issues на GitHub).
  1. Добавлена обработка исключений, чтобы избежать Access Violation, когда входная строка слишком длинная.
DelphiZXingQRCode вообще почти не содержит каких-то обработок исключений. Например, если закодированная строка не влезает в размер, определённый стандартом QR-кодирования, то программа попросту выдаёт Access Violation. Мне это показалось неправильным, и я добавил класс-исключение EQRMatrixTooLarge. Конечно, было бы интереснее как-то заранее ограничить максимальную длину строки, но тут есть один нюанс: она зависит от самих кодируемых данных и может колебаться.
  1. Добавлен отдельный модуль для получения графического представления QR-кода в разных форматах (битовая матрица, метафайл).
Новый модуль QRGraphics.pas содержит несколько функций: для отрисовки QR-кода на заданной канве (TCanvas), для генерации битовой матрицы и для создания метафайла. И ещё в демонстрационном примере (см. ниже) имеется кусочек кода на пару строк, который сохраняет картинку в формате JPEG (обработчик кнопки btnSaveToFile). Функция рисования умеет также добавлять к традиционной матрице «уголок», предусмотренный ГОСТ Р 56042-2014 (сотрудники Сбербанка в переписке ласково называют его «кочерга»). 
  1. Исправление ошибок, рефакторинг и другие улучшения кода.
Здесь было очень много исправлений косметического характера: удалены ненужные скобки после оператора if (наследие C++ или Java), бесполезные пары begin / end вокруг отдельной строки и т. п., исправлена неаккуратная структура классов, выброшены лишние объявления. Код сократился более чем на 200 строк, причём это с учётом разных добавлений и новых комментариев. Кому-то это может показаться бесполезным, но ведь гораздо удобнее читать код, не спотыкаясь на каждом шагу.
  1. Новый демонстрационный проект (программа для генерации QR-кодов).
Демонстрационный проект (TestApp) переделан настолько, что проще сказать «создан заново». Фактически его можно использовать как отдельную программу для создания QR-кодов на все случаи жизни (чем мы сами с успехом и пользуемся). Он теперь «умеет» показывать строку после преобразования кодировщиком (это важно, так как некоторые символы кодировщик из строки может удалить, а другие заменить), менять цвета для изображения QR-кода, копировать картинку в буфер обмена Windows и сохранять её на диске в разных форматах (BMP, JPEG, EMF).

Так что если вам нужна только простая программа для генерации QR-кодов, то вот прямая ссылка на exe-файл (на GitHub).

  1. Обеспечивается совместимость с разными версиями Delphi и Lazarus.
См. следующий параграф.

Системные требования: Delphi и Lazarus

Сохранена совместимость со старыми версиями Delphi (по крайней мере, до 7 включительно). Тестировалось на двух версиях: Delphi 7 и Delphi XE3 (других у меня просто нет). Всё компилируется без проблем.

Внесены некоторые исправления для обеспечения совместимости с Lazarus (проверялось на версии 1.2.6 для Windows). Основные модули (DelphiZXIngQRCode.pas, QR_URL.pas и QR_Win1251.pas) компилируются без изменений, а вот в модуле QRGraphics.pas имеется одна мелкая неприятность: в стандартных модулях Lazarus (в частности, Graphics.pas) отсутствует поддержка класса TMetafile. Это можно исправить, например, подключив к проекту (и добавив в секцию uses модуля QRGraphics.pas) модуль TADrawerWMF.pas, поставляемый в составе библиотеки TAChart, которая устанавливается вместе с Lazarus. Путь к файлу выглядит как-то так (под Windows): …\lazarus\components\tachart\tadrawerwmf.pas. В этом случае для компиляции модуля QRGraphics.pas достаточно убрать (закомментировать) в процедуре MakeMetafile строку Enhanced := True; (эта реализация класса TMetafile её не поддерживает).

Вторая особенность работы с Lazarus заключается в наличии ошибки (подтверждённой разработчиками) в классе TBitmap: вызов SaveToFile этого объекта при установленном в True значении свойства Monochrome приводит к исключению «image palette is too big or absent». Поэтому я просто поставил директиву условной компиляции, так что под Lazarus при сохранении картинки в этом формате размер получается в два десятка раз больше. Возможно, есть более элегантный способ.

Исходники демонстрационного проекта под Lazarus

Демонстрационный проект тоже можно откомпилировать под Lazarus для Windows — полагаю, что под другими ОС могут быть сложности. Я включил уже исправленный исходник в виде отдельного архива внутри папки TestApp. Отличия от версии для Delphi там такие:
  • выкинуто всё, что связано с полем FAltFixed (это было необязательно, но это всё равно имеет смысл только в Delphi и только в старых версиях),
  • в конец списка модулей в секции interface/uses модуля главной формы DelphiZXingQRCodeTestAppMainForm.pas добавлен модуль LclTypes (иначе будет ошибка в обработчике cmbEncodingDrawItem, связанная с тем, что тип TOwnerDrawState определён дважды),
  • из секции implementation/uses в том же файле удалён модуль jpeg (поддержка формата JPEG встроена в стандартные модули) и добавлен модуль для поддержки метафайлов (тот же, что в QRGraphics.pas, см. выше). Все пути в файлах настроек прописаны относительные или с использованием $(LazarusDir).

Применение

Печать счетов в Сбербанк

Собственно, с этого всё и началось. В сентябре наша бухгалтерия получила от Сбербанка предложение: печатать на счетах на оплату обучения (которые у нас печатаются тысячами) QR-код, чтобы сэкономить время на ввод данных при оплате и уменьшить вероятность ошибки. Не буду рассказывать, каково работать со Сбербанком (кто хоть раз имел дело с этой конторой, уже представляет их «оперативность» и «отзывчивость»: так, на e-mail, отправленный 12-го ноября, техподдержка ответила только 21-го, как будто письмо было послано не по электронной почте, а Почтой России). И вот, когда я наконец доделал соответствующую функцию в нашем софте, то в ответ на это от Сбербанка мы получили сообщение, что у них у самих ещё не сделана программа для чтения этих QR-кодов, так что минимум до Нового года можно (было) не спешить.

Итог: генерация и вставка кодов работает нормально. По крайней мере, валидатор на сайте Сбербанка (http://www.sbqr.ru/validator/index.html) распознаёт наши коды без ошибок. Программы для чтения QR-кодов с камеры мобильного тоже считывают закодированные данные без проблем.

Расписание занятий

Гуляя по коридорам нашего вуза я постоянно вижу студентов, которые фотографируют мобильниками расписание занятий. И вот возникла такая идея: снабдить расписание, которое вывешивается на стенд, QR-кодом со ссылкой на сайт, где то же расписание было бы в электронном виде (это удобнее). Начальство идею одобрило. Выглядит это примерно как на картинках: вариант для одиночной группы (код в «шапке») и вариант для курса в целом, чтобы повесить на доске отдельно. Не знаю, часто ли в вузах делают такое, но для нашего это пока экзотика.

(Любителей всё проверять на практике предупреждаю, что в QR-кодах на картинках ниже зашифрованы адреса, которые были актуальны на момент написания этой статьи, и, скорее всего, уже недоступны сейчас, когда вы её читаете.)


Расписание с кодом в «шапке»


Отдельный лист с набором кодов-ссылок

Полезные ссылки и благодарности

  1. Библиотека DelphiZXingQRCode: на сайте Debenu и в GitHub.
  2. Оригинальная кроссплатформенная и многофункциональная библиотека ZXing, поддерживающая множество форматов штрих-кодов: GitHub (текущая версия), Google Code (старая версия).
  3. Статья в Википедии про QR-коды, благодаря которой я осознал, что не стоит и пытаться написать такую библиотеку самостоятельно за разумное время.
  4. Статья на Хабрахабре, которая помогла понять, что всё не так страшно (и определить, почему неправильно записывается уровень коррекции ошибок).
Отдельное большое спасибо моей сестре Маше, которая не поленилась проверить и исправить мой кошмарный английский в файлах README.md и CHANGELOG.md.
К началу