Печать HTML-документа в Qt 5.8

23 января 2017 года вышла Qt версии 5.8, в которой появилась поддержка печати полноценных HTML-документов.

До этого возможность напечатать документ тоже была. Можно было:

  • Использовать метод QTextDocument::Print(). Но приходилось ограничиваться небольшими возможностями HTML и CSS.
  • Использовать метод QWebFrame::print(), который существовал пока не вышла Qt 5.6. Потом в Qt был окончательно заменен веб-движок и поддержка печати пропала.
  • Использовать печать в PDF с помощью метода QWebEnginePage::printToPdf(), который появился в Qt 5.7. Но печать в PDF — это не печать через принтер.
  • Вывести видимую часть страницы на печать методом QWebEngineView->render(). Но это плохой обходной путь, так как не вся страница будет выведена на печать и потребуется отобразить страницу.

В Qt 5.8 появился метод QWebEnginePage::print(), который выводит на печать HTML-документы без отрисовки в окне и поддерживает современные возможности HTML, CSS и JavaScript.

Следующий код выводит HTML-документ на принтер.

#include <QDebug>
#include <QPrinter>
#include <QPrintDialog>
#include <QPrinterInfo>
#include <QWebEnginePage>

void MainWindow::on_printButton_clicked()
{
    QPrinter * printer = new QPrinter();

    QPrintDialog printDialog(printer, this);
    if (printDialog.exec() != QDialog::Accepted) {
        return;
    }

    QWebEnginePage * page = new QWebEnginePage;

    page->setHtml("<html><body>Привет<body/></html>");

    connect(
        page,
        &QWebEnginePage::loadFinished,
        [page, printer] (bool ok) {
            if (!ok) {
                qDebug() << "Загрузка документа провалилась."; 
                delete page;
                delete printer;
                return;
            }

            page->print(printer, [page, printer](bool ok) {
                if (ok) {
                    qDebug() << "Документ напечатан.";
                }
                else {
                    qDebug() << "Печать документа провалилась.";
                }
        
                delete page;
                delete printer;
            });
        }
    );
}

В файле проекта (*.pro) нужно подключить два модуля с помощью следующей строчки.

QT += printsupport webenginewidgets

В коде используются лямбда-выражения, которые появились в C++11. Это конструкции вида: [...] (...) {...}, анонимные функции внутри функций. В Qt 5.8 по умолчанию включен C++11.

Класс QWebEnginePage обрабатывает, отрисовывает и печатает документы асинхронно с основным кодом программы, поэтому код в лямбда-функциях выполняется уже после того, как завершится метод on_printButton_clicked().

Надо внимательно следить за удалением объектов, чтобы не возникло утечки памяти и ошибки сегментации, особенно при печати нескольких документов в цикле. Объект принтера может быть одним, а выводимых на печать документов несколько. Тогда объект принтера надо удалять, после печати последнего документа, и не забывать, что печать происходит асинхронно. То есть функция print() завершается еще до вывода документа на печать.

Но и у этого способа печати есть огромный недостаток. К сожалению модуль Qt WebEngine, в который входит класс QWebEnginePage, недоступен при сборке проекта с помощью MinGW в Виндовс. Собрать проект в Виндовс можно только с помощью Visual Studio.

Простой способ отписать подписчика от рассылки

Это способ не требующий обработки на сервере. Отписку можно произвести вручную, требуется только, чтобы пользователь об этом заявил. Чтобы помочь пользователю, можно добавить в текст письма следующую ссылку:

<a href="mailto:example@example.ru?subject=unsubscribe">
    Отписаться от рассылки
</a>

Если пользователь нажмет ссылку, то ему будет предложено написать письмо на адрес example@example.ru с темой «unsubscribe». Далее ему достаточно просто нажать кнопку «Отправить». Если мы получаем, письмо с такой темой, то вручную удаляем подписчика из базы.

Django: шаблон в зависимости от режима отладки

Хочу привести рецепт того, как с помощью создания собственного шаблонного тега заставить меняться шаблон в зависимости от режима отладки.

Режимом отладки управляет переменная DEBUG в файле settings.py. Если DEBUG == True, то Django выводит отладочную информацию при возникновении ошибок.

Отладка должна быть выключена на боевом сервере и включена на сервере разработки. Это правило можно использовать, чтобы на основании переменной DEBUG, определять на каком сервере мы находимся.

В Django генерацией страниц из шаблонов занимаются представления. Представим ситуацию, когда есть базовый шаблон, использующийся во множестве представлений, и понадобилось изменять шаблон в зависимости от какого-то условия. Править все представления слишком муторно и можно что-нибудь пропустить. Тогда на помощь приходят собственные теги.

При написании статьи я использовал Django версии 1.8.

Собственный тег можно определить только в приложении, поэтому создадим приложение app_for_template_tags командой:


python3 manage.py startapp app_for_template_tags

Приложение необходимо добавить в INSTALLED_APPS в файле settings.py:

INSTALLED_APPS = (
    # Другие приложения,
    'app_for_template_tags',
)

Теги должны определяться в пакете templatetags, поэтому надо создать папку templatetags с пустым файлом __init__.py в каталоге app_for_template_tags.

Создадим модуль в котором будет определение тега. Пусть модуль называется debug_mode_tag.py.

Содержимое debug_mode_tag.py:

from django import template
from django.conf import settings

register = template.Library()

@register.assignment_tag
def debug_mode_tag():
     return settings.DEBUG

Имя функции debug_mode_tag соответствует имени нового тега. Имя файла не обязательно должно совпадать с именем функции. Кроме того функций может быть несколько.

Если разработка ведется на сервере разработчика и включен режим отладки, то функция debug_mode_tag вернет True, иначе False.

В шаблоне тег используется следующим образом:

{% load debug_mode_tag %}

{% debug_mode_tag as debug_mode %}

{% if debug_mode %}
  <!-- Html-код, который будет
  показан, если включена отладка. -->
{% endif %}

В первой строке подключается модуль debug_mode_tag. Во второй строке создается переменная debug_mode, значение которой будет равно значению, которое вернула функция debug_mode_tag.

Итог

Для решения понадобилось создать файловую структуру:

Django тег debug_mode_tag

Создать функцию в модуле debug_mode_tag.py и добавить приложение в settings.py.

Подробное описание создания тегов можно почитать в документации на Django.

Вращающееся изображение на сайте

Пример вращающегося изображения.

С помощью библиотеки jQuery Reel объект на изображении можно заставить вращаться. Вращение можно организовать вокруг вертикальной и горизонтальной оси. Объект может вращаться как сам по себе так и с помощью мыши.

Далее будет описано как создать изображение с объектом вращающемся вокруг вертикальной оси.

Сперва требуется создать некоторое количество изображений объекта под разными углами. Изображения должны иметь одинаковую высоту и ширину.

Затем объединить все изображения в коллаж. Например:

image-reel(485x254)

Порядок изображений в коллаже: слева направо и сверху вниз.

Библиотека предоставляет возможность обойтись без коллажа, но этот способ здесь не рассматривается.

Существует простой способ создания коллажей с помощью программы ImageMagick. Файлы с изображением объекта под разными углами лучше именовать следующим образом:

  • image-001.jpg
  • image-002.jpg
  • image-003.jpg
  • и т.д.

Последовательность нумерации должна совпадать с последовательностью вращения.

ImageMagick предоставляет консольную утилиту montage для склейки изображений. Чтобы ей воспользоваться, следует запустить консоль (командную строку), перейти в папку с изображениями и выполнить команду:

montage *.jpg -geometry 485x254 collage.jpg

где:

  • *.jpg — указывает, что требуется объединить все изображения с расширением jpg в данной папке. Вместо этого можно просто перечислить изображения через пробел: image-001.jpg image-002.jpg image-003.jpg и т. д.
  • 485x254 — размер исходных изображений в пикселях (ширина x высота). Если указать другой размер, то каждое изображение будет отмасштабировано до указанного размера. Далее значения ширины и высоты будут использованы в html-коде.
  • collage.jpg — коллаж, итоговое изображение.

Чтобы встроить вращающееся изображение в html-страницу, следует воспользоваться следующим кодом:

<html>
  <head>
    <script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
    <script src="//code.vostrel.cz/jquery.reel.js"></script>
  </head>
  <body>
    <img src="image-001.jpg" width="485" height="254"
      class="reel"
      data-image="collage.jpg"
      data-frames="24"
      data-footage="6"
      data-revolution="800">
  </body>
</html>

В <head> подключается библиотека jQuery и плагин Reel. В <body> изображение добавляется стандартным способом с помощью тега <img>. С помощью атрибута src указывается изображение, которое будет отображаться пока библиотека или коллаж не загрузились, а также если что-то пошло не так и вращение не заработало.

Другие атрибуты:

  • width="485" — ширина одного изображения в коллаже.
  • height="254" — высота одного изображения в коллаже.
  • class="reel" — связь с библиотекой.
  • data-image="collage.jpg" — коллаж.
  • data-frames="24" — количество изображений в коллаже.
  • data-footage="6" — количество изображений по горизонтали.
  • data-revolution="800" — количество пикселей, которое должен пройти указатель мыши для полного оборота.

Кроме этих атрибутов библиотека предоставляет множество других, с помощью которых можно заставить изображение вращаться без использования мыши, задать скорость этого вращения, указать начальное изображение в коллаже или заставить изображение вращаться вокруг горизонтальной оси. Со всеми атрибутами можно ознакомиться на странице библиотеки.

Без коллажа

Можно обойтись без коллажа, воспользовавшись атрибутом data-images, в котором можно задать диапазон изображений.

<html>
  <head>
    <script src="//code.jquery.com/jquery-2.1.3.min.js"></script>
    <script src="//code.vostrel.cz/jquery.reel.js"></script>
  </head>
  <body>
    <img src="image-001.jpg"
      class="reel"
      data-images="image-###.jpg|1..100"
      data-revolution="800">
  </body>
</html>

Вертикальная черта в атрибуте data-images отделяет шаблон имени файла изображения от диапазона чисел, которые задают номер первого и последнего изображения. Алгоритм вращателя заменяет решетки в имени файла на числа из диапазона. Между числами в диапазоне должны быть две точки.