How to print multiple instances of QTextBrowser into one PDF file? - c++

the QT application I'm working on comes with a tutorial. Each chapter is a stand-alone HTML file, each file can span multiple pages. Now I want to print them into one single PDF file (with page numbers).
My naive approach was this, but it's wrong:
#include <QApplication>
#include <QPrinter>
#include <QTextBrowser>
#include <QUrl>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPrinter printer;
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("/tmp/test.pdf");
QTextBrowser *tp = new QTextBrowser();
tp->setSource(QUrl("qrc:///help/tutorial_item_1.html"));
tp->print(&printer);
tp->setSource(QUrl("qrc:///help/tutorial_item_2.html"));
tp->print(&printer);
tp->setSource(QUrl("qrc:///help/tutorial_item_3.html"));
tp->print(&printer);
// etc...
}
However, this will restart the printer on each print() call, starting with a new PDF file, overwriting the old one.
What is a simple solution to print all HTML into one PDF file, using QT?

Developping on your "naive approach", I could print concatenated html files by appending several pages to a parent QTextEdit. It would probably also work utilizing a second QTextBrowser instead.
// ...
QTextBrowser *tp = new QTextBrowser();
QTextEdit te;
tp->setSource(QUrl("qrc:///help/tutorial_item_1.html"));
te.append(tp->toHtml());
tp->setSource(QUrl("qrc:///help/tutorial_item_2.html"));
te.append(tp->toHtml());
te.print(&printer);
// ...

You can achieve this by rendering your contents on a QPainter object linked to the QPrinter device
// Sample code ahead ~>
QPrinter printer;
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName("C:\\test.pdf");
printer.setFullPage(true);
printer.setPageSize(QPrinter::A4);
QTextBrowser tb;
QPainter painter;
painter.begin(&printer);
QRect rect = printer.pageRect();
tb.resize(rect.width(), rect.height());
{
QFile file("C:\\test1.html");
if(file.open(QIODevice::ReadOnly)) {
QTextStream ts(&file);
tb.setHtml(ts.readAll());
file.close();
tb.render(&painter, QPoint(0,0));
}
}
if(printer.newPage() == false)
qDebug() << "ERROR";
{
QFile file("C:\\test2.html");
if(file.open(QIODevice::ReadOnly)) {
QTextStream ts(&file);
tb.setHtml(ts.readAll());
file.close();
tb.render(&painter, QPoint(0,0));
}
}
painter.end();

Related

black rect instead of QtextDocument content in QPrinter pdf output

So I needed border in QtextFrame in a pdf (QPrinter's pdf output). Following is the code I used
#include <QPainter>
#include <QTextFrameFormat>
#include <QTextCursor>
#include <QTextFrame>
#include <QTextEdit>
#include <QPrinter>
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QTextDocument doc;
QPixmap map(1024, 1024);
map.fill(Qt::white);
QPainter p;
p.begin(&map);
p.fillRect(QRect(0,0, 4, map.height()), QBrush(Qt::red));
p.end();
QTextFrameFormat frameFormat;
QBrush bruh(map);
bruh.setColor(Qt::transparent);
frameFormat.setBackground(bruh);
frameFormat.setPadding(6);
auto cur = new QTextCursor(&doc);
auto frame = cur->insertFrame(frameFormat);
auto curf = new QTextCursor(frame);
curf->insertText("Hello this is qt program!");
QPrinter pdf;
pdf.setOutputFileName("E:\\test.pdf");
pdf.setOutputFormat(QPrinter::PdfFormat);
doc.print(&pdf);
QTextEdit edt;
edt.setDocument(&doc);
edt.show();
return a.exec();
}
But the pdf output is different from the what is shown in QTextEdit
pdf (Only a single black line no content)
Text Edit output which is what I needed

QTextDocument::DrawContents skips resources?

I have this setup:
// ...
// variable document is a QTextDocument* which has some 'RichText' + 'Images'
QTextEdit textEdit;
textEdit.setDocument(document);
textEdit.setLineWrapMode(QTextEdit::LineWrapMode::NoWrap);
auto image = QImage(document->size().width(), document->size().height(),
QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter painter(&image);
document->drawContents(&painter);
// ...
I'm doing this to have my text rendered in a long horizontal QImage (hence the "NoWrap" LineWrapMode), so I can select a small part of it at a time with QImage::copy(QRect) and create a scrolling text effect.
The reason I'm doing it this way is that I need to have a QImage at the end which then I would feed its buffer (QImage::bits()) to the hardware that I'm using as my final output.
So it works great, it displays formatted text with fonts and colors and everything except for the images, it seems to skip them, notice the file icon in "result of text with image" picture.
This is text only in editor
This is result of text only
This is text with image in editor
This is result of text with image
This is how I'm inserting images to my QTextDocument:
QImage image(url.toLocalFile());
if (image.isNull())
return;
image = image.scaledToHeight(getDocumentHeight(), Qt::SmoothTransformation);
auto filename = QUrl(url.fileName());
textEdit->document()->addResource(QTextDocument::ImageResource, filename, image);
textEdit->textCursor().insertImage(filename);
So I don't think it's because "DrawContents" fails to find the image resource file or something like this.
What should I do? Is there something that I'm missing? Any kind of help in the matter is highly appreciated! ;)
In the following code I show how an image should be loaded, then save it to a file, probably the error is that you have not finished painting, for this you must call painter.end() or delete painter from memory.
main.cpp
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QWidget widget;
QVBoxLayout vlayout(&widget);
QTextEdit textEdit;
QPushButton button("save image");
QPushButton loadButton("Load and Insert");
vlayout.addWidget(&loadButton);
vlayout.addWidget(&textEdit);
vlayout.addWidget(&button);
widget.show();
textEdit.append("some text");
QObject::connect(&loadButton, &QPushButton::clicked,[&textEdit](){
QString filename = QFileDialog::getOpenFileName(&textEdit, "Select", "", "*.png");
if(!filename.isEmpty()){
QImage image(filename);
QUrl url = QUrl::fromLocalFile(filename);
image = image.scaledToHeight(100, Qt::SmoothTransformation);
textEdit.document()->addResource(QTextDocument::ImageResource, url, image);
textEdit.textCursor().insertImage(image);
}
});
QObject::connect(&button, &QPushButton::clicked, [&textEdit](){
QImage image(textEdit.document()->size().toSize() , QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
QPainter painter(&image);
textEdit.document()->drawContents(&painter);
painter.end();
image.save("image.png");
});
return a.exec();
}

QtWidget Convert HTML to PDF

The code below is based on this question.
But I still can't get it to work. From basic debugging I think there is something wrong with passing the filenames to the function. There is no reaction at all when I clicked on the "Print as PDF" button.
The main code is as below. If you would like to see further, feel free to ask them from me. Thanks.
Part of main.cpp
QApplication app(argc, argv);
//Get filename for all documents
QString filename = get_filename(Var_STR);
QString txt = txt_filename(filename);
QString csv = csv_filename(filename);
QString html = html_filename(filename);
//Print report in .txt, .csv and .html accordingly
heading(txt, csv, html, Var_STR, Var_INT[6], Var_INT[16], Var_INT[17]);
pp_prerinse(txt, csv, html, Var_INT[18], Var_INT[19], Var_INT[20], Var_INT[21], Var_INT[22], Var_INT[23], Var_INT[24], Var_INT[25], Var_INT[26], Var_REAL[11], Var_INT[30], Var_INT[31], Var_INT[32]);
pp_wash(txt, csv, html, Var_STR, Var_INT[33], Var_INT[34], Var_INT[35], Var_INT[36], Var_INT[37], Var_INT[38], Var_REAL[23], Var_INT[41], Var_INT[42], Var_INT[43], Var_REAL[28], Var_INT[47], Var_INT[48], Var_INT[49], Var_INT[50], Var_INT[51], Var_INT[52], Var_INT[53], Var_INT[54], Var_INT[55], Var_REAL[40]);
pp_rinse(txt, csv, html, Var_INT[59], Var_INT[60], Var_INT[61], Var_INT[62], Var_INT[63], Var_INT[64], Var_INT[65], Var_INT[66], Var_INT[67], Var_INT[68], Var_INT[69], Var_INT[70], Var_INT[71], Var_INT[72], Var_INT[73], Var_INT[74], Var_INT[75], Var_INT[76], Var_INT[77], Var_REAL[61]);
phase_prerinse(txt, csv, html, Var_STR, Var_REAL[64], Var_REAL[67], Var_REAL[70]);
wash(txt, csv, html, Var_STR, Var_REAL[73], Var_REAL[75], Var_REAL[77], Var_REAL[79], Var_REAL[81], Var_REAL[83], Var_REAL[85], Var_REAL[87], Var_REAL[89], Var_REAL[91], Var_REAL[94], Var_REAL[97]);
rinse(txt, csv, html, Var_INT[129], Var_STR);
basin_flush(txt, csv, html, Var_STR);
alarms_code(txt, csv, html, Var_REAL[100], Var_REAL[103], Var_REAL[106]);
tail(txt, csv, html);
//Report preview UI and convert .html to .pdf
QDir htmlpath = QFileInfo(html).absoluteDir();
MainWindow mainWindow((htmlpath.absolutePath())+"/"+html, filename);
mainWindow.setWindowTitle("Print Preview");
mainWindow.showMaximized();
return app.exec();
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QObject>
#include <QMainWindow>
#include <QWebView>
#include <QUrl>
#include <QPushButton>
#include <QString>
#include "windows.h"
#include <algorithm>
#include "qt_windows.h"
#include "qwindowdefs_win.h"
#include <ShellAPI.h>
namespace Ui
{
class MainWindow;
class QPrinter;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QString previewfile = "", QString file = "", QWidget *parent = 0);
virtual ~MainWindow();
public slots:
void buttonPrint(QString filepath);
void buttonCancel();
private:
QWebView *m_pWebView; //Preview of the report layout
QPushButton *m_button; //Print button
QPushButton *n_button; //Cancel button
QString printfile;
QString filepath;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "stdafx.h"
#include "mainwindow.h"
#include <QPrinter>
#include <QTextDocument>
#include <QTextStream>
#include <QFile>
#include <QDir>
MainWindow::MainWindow(QString previewfile, QString file, QWidget *parent)
: QMainWindow(parent)
{
//Open html version of report
m_pWebView = new QWebView(this);
//Set position and size
m_pWebView->setGeometry(0, 0, 1000, 735);
m_pWebView->load(QUrl::fromLocalFile(previewfile));
//Create "print" button
m_button = new QPushButton("Print as PDF", this);
//Set location of button
m_button->setGeometry(QRect(QPoint(1100, 150), QSize(75, 23)));
//Print button action
connect(m_button, SIGNAL(clicked()), this, SLOT(buttonPrint(file)));
n_button = new QPushButton("Cancel", this);
n_button->setGeometry(QRect(QPoint(1100, 179), QSize(75, 23)));
connect(n_button, SIGNAL(clicked()), this, SLOT(buttonCancel()));
}
void MainWindow::buttonPrint(QString filepath)
{
QFile htmlfile(filepath+".html");
if(htmlfile.open(QIODevice::ReadOnly | QIODevice::Text))
{
QString htmlContent;
QTextStream in(&htmlfile);
htmlContent = in.readAll();
QTextDocument *document = new QTextDocument();
document->setHtml(htmlContent);
QPrinter printer(QPrinter::HighResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(filepath+".pdf");
document->print(&printer);
delete document;
buttonCancel();
}
}
void MainWindow::buttonCancel()
{
QApplication::quit();
}
MainWindow::~MainWindow()
{
}
I recommend you to porting from QTWebKit to QTWebEngine which use Chromium as native browser, faster and newst. Check for details:
http://doc.qt.io/qt-5/qtwebenginewidgets-qtwebkitportingguide.html
With 5.6, Qt WebKit and Qt Quick 1 will no longer be supported and are dropped from the release. Qt 5.7 integrates printsupport for browser so let's try to use Chromium:
First of all, include:
QT += webengine webenginewidgets printsupport
Now, try something like this:
// Create the webview
QWebEngineView *webView = new QWebEngineView(this);
webView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
ui->centralWidget->layout()->addWidget(webView);
// Path to the HTML file
const QString filePath = QFileDialog::getOpenFileName(this, "Import HTML", ".", "HTML Files (*.html)");
// Check if the file exist
QFileInfo fileInfo(filePath);
if (!fileInfo.isFile()) {
qDebug() << "Warning, file not found!";
}
// Preview the HTML file
webView->load(QUrl::fromLocalFile(fileInfo.filePath()));
// How to print the page?
// Get the path to the pdf file
const QString pdfPath = QFileDialog::getSaveFileName(this, "Export to pdf", ".", "PDF Files (*.pdf)");
// Print the page in pdf format
webView->page()->printToPdf(pdfPath);

QLabel with pixmap image getting blurred

I have an image which I need to display as the background of a QLabel. This is my code (culled from Qt documentation here):
#include <QtWidgets>
#include "imageviewer.h"
ImageViewer::ImageViewer()
{
imageLabel = new QLabel;
imageLabel->setBackgroundRole(QPalette::Base);
imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
imageLabel->setScaledContents(true);
setCentralWidget(imageLabel);
createActions();
createMenus();
resize(570,357);
}
bool ImageViewer::loadFile(const QString &fileName)
{
QImageReader reader(fileName);
const QImage image = reader.read();
if (image.isNull()) {
QMessageBox::information(this, QGuiApplication::applicationDisplayName(),
tr("Cannot load %1.").arg(QDir::toNativeSeparators(fileName)));
setWindowFilePath(QString());
imageLabel->setPixmap(QPixmap());
imageLabel->adjustSize();
return false;
}
imageLabel->setPixmap(QPixmap::fromImage(image).scaled(size(),Qt::KeepAspectRatio,Qt::SmoothTransformation));
return true;
}
void ImageViewer::open()
{
QStringList mimeTypeFilters;
foreach (const QByteArray &mimeTypeName, QImageReader::supportedMimeTypes())
mimeTypeFilters.append(mimeTypeName);
mimeTypeFilters.sort();
const QStringList picturesLocations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation);
QFileDialog dialog(this, tr("Open File"),
picturesLocations.isEmpty() ? QDir::currentPath() : picturesLocations.last());
dialog.setAcceptMode(QFileDialog::AcceptOpen);
dialog.setMimeTypeFilters(mimeTypeFilters);
dialog.selectMimeTypeFilter("image/jpeg");
while (dialog.exec() == QDialog::Accepted && !loadFile(dialog.selectedFiles().first())) {}
}
void ImageViewer::createActions()
{
openAct = new QAction(tr("&Open..."), this);
openAct->setShortcut(tr("Ctrl+O"));
connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
}
void ImageViewer::createMenus()
{
fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(openAct);
menuBar()->addMenu(fileMenu);
}
Header file:
#ifndef IMAGEVIEWER_H
#define IMAGEVIEWER_H
#include <QMainWindow>
class QAction;
class QLabel;
class QMenu;
class QScrollArea;
class QScrollBar;
class ImageViewer : public QMainWindow
{
Q_OBJECT
public:
ImageViewer();
bool loadFile(const QString &);
private slots:
void open();
private:
void createActions();
void createMenus();
QLabel *imageLabel;
QAction *openAct;
QMenu *fileMenu;
};
#endif
Main:
#include <QApplication>
#include <QCommandLineParser>
#include "imageviewer.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGuiApplication::setApplicationDisplayName(ImageViewer::tr("Image Viewer"));
ImageViewer imageViewer;
imageViewer.show();
return app.exec();
}
As you can see, the viewport size is 570 by 357. The image I am using is this (size is 2560 by 1600, both have an aspect ratio of almost 1.6, too big to upload here, so will upload a screenshot of the pic):
But when I open the app and add the image there, the image I get in the QLabel is pretty blurred:
How do I make the image in the label as well defined as the actual image (it is in bmp format, so you have to change the mime type to bmp in the file open dialog in Mac)?
When I do this task through QPainter, i.e making a QImage out of the image file, then passing it to the painter and calling update it comes up fine. But I need to be able to zoom the image inline when clicked (which I am doing by calling resize() on the QLabel), and the QPainter way does not let me zoom the image.
Platform - OS X 10.10 (Retina Display), Qt 5.3.1, 32 bit.
You are running into a known bug in QLabel on a Retina display Mac in Qt 5.3:
https://bugreports.qt.io/browse/QTBUG-42503
From your image, it looks like you are simply getting a non-retina version of your image when you use QLabel, but if you code it manually in the QPainter you're getting the full resolution of your source QImage. If thats the case, then this is indeed caused by this bug.
You have two solutions:
Roll your own solution, don't use QLabel. There is no reason you can't easily "zoom the image inline" without using QLabel (just use a custom QWidget class with an overrided PaintEvent).
Upgrade to a version of Qt where this bug has been fixed. This is probably the right solution anyway, unless there are any regressions in the latest version that cause a problem in your application. According to the bug report, this issues was fixed in v5.5.0, so the latest v5.5.1 should work fine.
An example of the first approach (I'll leave the header out the header file for brevity, its pretty simple):
void ImageWidget::setImage(QImage image)
{
//Set the image and invalidate our cached pixmap
m_image = image;
m_cachedPixmap = QPixmap();
update();
}
void ImageWidget::paintEvent(QPaintEvent *)
{
if ( !m_image.isNull() )
{
QSize scaledSize = size() * devicePixelRatio();
if (m_cachedPixmap.size() != scaledSize)
{
QImage scaledImage = m_image.scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
m_cachedPixmap = QPixmap::fromImage(scaledImage);
m_cachedPixmap.setDevicePixelRatio(devicePixelRatio());
}
QPainter p(this);
p.drawPixmap(0, 0, m_cachedPixmap);
}
}
This is simply a very basic widget that just draws a QImage to its full size, respecting the DevicePixelRatio, and hence taking advantage of Retina resolution. Note: coded this on a non-retina machine, so I can't guarantee that it works on Retina, but I got the basic implementation from the Qt 5.5.1 fix for QLabel. Like QLabel, it also caches the Pixmap so it doesn't have to re-scale every time paintEvent is called, unless the widget has actually been resized.
You compress the image more than 4-fold (2560 -> 570) and it seems there are small details in the image impossible to retain after shriking this much.
Actually, you get more or less what you can expect after shrinking an image so much.

Qt QFont selection of a monospace font doesn't work

i'm trying to make a qt widget that shows a table of qlabels displaying hex numbers.
i pass the numbers to the labels as qstrings ready to be printed and the labels work properly but the font type is the system default (a sans serif) that has different letter sizes, so the numbers containing "A-F" digits are no more aligned with the other numbers...
i initially create the font with the function:
static const QFont getMonospaceFont(){
QFont monospaceFont("monospace"); // tried both with and without capitalized initial M
monospaceFont.setStyleHint(QFont::TypeWriter);
return monospaceFont;
}
and create a custom QLabel class that has this constructor:
monoLabel(QWidget *parent = 0, Qt::WindowFlags f = 0) : QLabel(parent, f) {
setTextFormat(Qt::RichText);
setFont(getMonospaceFont());
}
but it doesn't work, so i add to the main file
QApplication app(argn, argv);
app.setFont(monoLabel::getMonospaceFont(), "monoLabel");
and again the font remains unchanged..
i searched the net for font-setting problems with QLabels but i seem to be the only one who doesn't get them to work properly..
what am i doing wrong??
You probably want a Monospace style hint, not Typewriter. The following works for me on OS X under Qt 4 and 5.
Setting QLabel to rich text is unnecessary for your application.
Note that QFontInfo::fixedPitch() is not the same as QFont::fixedPitch(). The latter lets you know whether you requested a fixed pitch font. The former indicated whether you actually got a fixed pitch font.
// https://github.com/KubaO/stackoverflown/tree/master/questions/label-font-18896933
// This project is compatible with Qt 4 and Qt 5
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QtWidgets>
#endif
bool isFixedPitch(const QFont &font) {
const QFontInfo fi(font);
qDebug() << fi.family() << fi.fixedPitch();
return fi.fixedPitch();
}
QFont getMonospaceFont() {
QFont font("monospace");
if (isFixedPitch(font)) return font;
font.setStyleHint(QFont::Monospace);
if (isFixedPitch(font)) return font;
font.setStyleHint(QFont::TypeWriter);
if (isFixedPitch(font)) return font;
font.setFamily("courier");
if (isFixedPitch(font)) return font;
return font;
}
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
QString text("0123456789ABCDEF");
QWidget w;
QVBoxLayout layout(&w);
QLabel label1(text), label2(text);
label1.setFont(getMonospaceFont());
layout.addWidget(&label1);
layout.addWidget(&label2);
w.show();
return a.exec();
}