Профессиональная реализация многопоточного асинхронного конвертера HTML в PDF на C++. Решение обеспечивает потокобезопасную работу с библиотекой wkhtmltopdf, которая не поддерживает конкурентный доступ из нескольких потоков. Конвертер предназначен для высоконагруженных сред, где требуется параллельная обработка множества документов.
- Singleton-обертка: Гарантирует единственный экземпляр конвертера в процессе
- Выделенный рабочий поток: Все операции с wkhtmltopdf выполняются в одном потоке
- Асинхронная очередь задач: Позволяет добавлять задачи конвертации из multiple потоков
- Двойной интерфейс: Поддержка как синхронных, так и асинхронных вызовов
Библиотека wkhtmltopdf имеет следующие ограничения:
- Не поддерживает инициализацию в разных потоках
- Не является потокобезопасной даже при использовании мьютексов
- Требует вызова инициализации и деинициализации в одном потоке
// Все операции с wkhtmltopdf выполняются в рабочем потоке
void workerThreadFunction() {
// Инициализация wkhtmltopdf в worker-потоке
if (wkhtmltopdf_init(0) != 1) {
std::cerr << "Failed to initialize wkhtmltopdf" << std::endl;
return;
}
// Обработка задач из очереди
while (running_) {
// Извлечение и выполнение задач конвертации
}
wkhtmltopdf_deinit();
}- Компилятор C++17 или новее
- CMake 3.10+
- Библиотека wkhtmltopdf (libwkhtmltox)
- Поддерживаемые платформы: Linux, Windows, macOS
- wkhtmltopdf: Основная библиотека для конвертации HTML в PDF
- nlohmann/json: Для генерации статистики работы (опционально)
sudo apt-get update
sudo apt-get install libwkhtmltox-devg++ -std=c++17 -pthread -O2 -lwkhtmltox \
html2pdf_converter.cpp wkhtml2pdf_wrapper.cpp main.cpp \
-o html2pdf_converterexport LD_LIBRARY_PATH=/path/to/wkhtmltopdf/lib:$LD_LIBRARY_PATH
g++ -std=c++17 -pthread -O2 -L/path/to/wkhtmltopdf/lib -lwkhtmltox \
-I/path/to/wkhtmltopdf/include \
html2pdf_converter.cpp wkhtml2pdf_wrapper.cpp main.cpp \
-o html2pdf_converter#include "wkhtml2pdf_wrapper.hpp"
int main() {
// Инициализация конвертера (должна быть вызвана из главного потока)
auto& converter = WkHtmlToPdfWrapper::getInstance();
if (!converter.initialize()) {
std::cerr << "Failed to initialize PDF converter" << std::endl;
return -1;
}
// Использование конвертера...
// Завершение работы
converter.shutdown();
return 0;
}#include "wkhtml2pdf_wrapper.hpp"
void convertSimpleDocument() {
auto& converter = WkHtmlToPdfWrapper::getInstance();
converter.initialize();
// Синхронная конвертация
bool success = converter.convertSync(
"input.html",
"output.pdf"
);
if (success) {
std::cout << "Conversion successful" << std::endl;
} else {
std::cerr << "Conversion failed" << std::endl;
}
converter.shutdown();
}Главный класс-обертка для потокобезопасной работы с wkhtmltopdf.
class WkHtmlToPdfWrapper {
public:
using Callback = std::function<void(bool, const std::string&)>;
// Singleton методы
static WkHtmlToPdfWrapper& getInstance();
// Управление жизненным циклом
bool initialize();
void shutdown(bool wait_for_completion = true);
// Методы конвертации
bool convertAsync(const std::string& input_html_path,
const std::string& output_pdf_path,
Callback callback = nullptr);
bool convertSync(const std::string& input_html_path,
const std::string& output_pdf_path);
// Утилиты
bool isInitialized() const;
std::string getStats() const;
};Класс для непосредственной конвертации с настройками.
class HtmlToPdfConverter {
public:
struct Options {
std::string page_size; // A4, Letter, Legal, etc.
std::string orientation; // Portrait, Landscape
int dpi; // Разрешение для изображений
int margin_top; // Верхнее поле в мм
int margin_bottom; // Нижнее поле в мм
int margin_left; // Левое поле в мм
int margin_right; // Правое поле в мм
double zoom; // Коэффициент масштабирования
int minimum_font_size; // Минимальный размер шрифта
bool disable_smart_shrinking; // Отключить умное сжатие
bool enable_local_file_access; // Разрешить доступ к локальным файлам
bool grayscale; // Чёрно-белый режим
bool lowquality; // Режим низкого качества
Options(); // Конструктор с значениями по умолчанию
};
static bool convertFile(const std::string& input_html_path,
const std::string& output_pdf_path,
const Options& opts = Options());
};// Инициализация (обязательна перед использованием)
bool initialize();
// Завершение работы
void shutdown(bool wait_for_completion = true);// Асинхронная конвертация с callback
bool convertAsync(const std::string& input_html_path,
const std::string& output_pdf_path,
Callback callback = nullptr);
// Синхронная конвертация (блокирующая)
bool convertSync(const std::string& input_html_path,
const std::string& output_pdf_path);HtmlToPdfConverter::Options opts;
opts.page_size = "A4";
opts.orientation = "Landscape";
opts.margin_top = 20;
opts.margin_bottom = 20;
opts.dpi = 150;
opts.zoom = 1.5;
opts.enable_local_file_access = true;#include "wkhtml2pdf_wrapper.hpp"
#include <thread>
#include <vector>
void convertMultipleDocuments() {
auto& converter = WkHtmlToPdfWrapper::getInstance();
converter.initialize();
std::vector<std::thread> threads;
std::vector<std::string> documents = {
"doc1.html", "doc2.html", "doc3.html", "doc4.html"
};
// Запуск конвертации в нескольких потоках
for (size_t i = 0; i < documents.size(); ++i) {
threads.emplace_back([&converter, i, &documents]() {
std::string output_file = "output_" + std::to_string(i) + ".pdf";
bool success = converter.convertSync(documents[i], output_file);
if (success) {
std::cout << "Converted " << documents[i] << " to " << output_file << std::endl;
} else {
std::cerr << "Failed to convert " << documents[i] << std::endl;
}
});
}
// Ожидание завершения всех потоков
for (auto& thread : threads) {
thread.join();
}
converter.shutdown();
}#include "wkhtml2pdf_wrapper.hpp"
void asyncConversionExample() {
auto& converter = WkHtmlToPdfWrapper::getInstance();
converter.initialize();
// Callback функция для обработки результатов
auto callback = [](bool success, const std::string& error_message) {
if (success) {
std::cout << "Conversion completed successfully" << std::endl;
} else {
std::cerr << "Conversion failed: " << error_message << std::endl;
}
};
// Асинхронная конвертация
converter.convertAsync("input.html", "output.pdf", callback);
// Можно продолжать выполнение других задач...
std::this_thread::sleep_for(std::chrono::seconds(2));
converter.shutdown(true); // Ждем завершения всех задач
}#include "wkhtml2pdf_wrapper.hpp"
#include "html2pdf_converter.hpp"
void batchProcessingWithOptions() {
auto& wrapper = WkHtmlToPdfWrapper::getInstance();
wrapper.initialize();
// Настройки для разных типов документов
HtmlToPdfConverter::Options report_opts;
report_opts.page_size = "A4";
report_opts.orientation = "Portrait";
report_opts.margin_top = 20;
report_opts.margin_bottom = 20;
HtmlToPdfConverter::Options presentation_opts;
presentation_opts.page_size = "A4";
presentation_opts.orientation = "Landscape";
presentation_opts.margin_top = 10;
presentation_opts.margin_bottom = 10;
// Пакетная конвертация
std::vector<std::pair<std::string, std::string>> documents = {
{"report.html", "report.pdf"},
{"presentation.html", "presentation.pdf"},
{"manual.html", "manual.pdf"}
};
for (const auto& doc : documents) {
bool success = wrapper.convertSync(doc.first, doc.second);
std::cout << doc.first << " -> " << doc.second
<< ": " << (success ? "SUCCESS" : "FAILED") << std::endl;
}
wrapper.shutdown();
}// Получение статистики работы
std::string stats = converter.getStats();
std::cout << "Converter statistics:" << std::endl;
std::cout << stats << std::endl;Пример вывода статистики:
{
"initialized": true,
"running": true,
"total_tasks": 15,
"completed_tasks": 12,
"failed_tasks": 1,
"queue_size": 2,
"last_error": ""
}