Can't generate pdf with acceptable output quality using Qt

时光毁灭记忆、已成空白 提交于 2020-01-01 14:55:12

问题


I'm trying to generate a pdf using Qt5 under Windows. My document contains texts, images and charts. As I'm familiar with Qt and Qwt, I believed the best strategy was to create a QWidget with my document layout and simply print it. But I face problems and could not end up with an acceptable result.

Here is my MCVE, a simple page document with:

  • A header with title and image
  • A piece of text
  • A simple chart

Based on Qt document and How can I print a QWidget in Qt?, I ended up with this code:

main.cpp:

#include <QApplication>
#include <QIcon>
#include <QDesktopServices>
#include <QWidget>
#include <QPrinter>
#include <QPainter>
#include <QPagedPaintDevice>
#include <QUrl>
#include "ui_report.h"

#include "qwt_plot.h"
#include "qwt_plot_curve.h"
#include "qwt_plot_canvas.h"
#include "qwt_point_data.h"
#include "qwt_legend.h"

#include <sstream>
#include <memory>

bool printWidget( QWidget& widget, bool highResolution, const std::string& fileName )
{
    QPrinter printer( highResolution ? QPrinter::HighResolution : QPrinter::ScreenResolution );

    printer.setOutputFormat(QPrinter::PdfFormat);
    printer.setOrientation(QPrinter::Portrait);
    printer.setPaperSize(QPrinter::A4);
    printer.setPageMargins(15,15,15,15,QPrinter::Millimeter);
    printer.setFullPage(true);
    printer.setOutputFileName(fromSDEString(fileName.c_str()));

    QPainter painter(&printer);

    double xscale = printer.pageRect().width()/double(widget.width());
    double yscale = printer.pageRect().height()/double(widget.height());
    double scale = qMin(xscale, yscale);
    painter.translate(printer.paperRect().x() + printer.pageRect().width()/2,
                      printer.paperRect().y() + printer.pageRect().height()/2);
    painter.scale(scale, scale);
    painter.translate(-widget.width()/2, -widget.height()/2);

    widget.render(&painter, QPoint(), QRegion(), QWidget::DrawChildren);

    return painter.end();
}

bool generateReport( bool drawWithPrinterResolution, bool printHighResolution, const std::string& fileName )
{
    QWidget widget;
    Ui::Report ui;
    ui.setupUi( &widget );

    if ( drawWithPrinterResolution )
    {
        QPrinter printer( printHighResolution ? QPrinter::HighResolution : QPrinter::ScreenResolution );

        printer.setOrientation(QPrinter::Portrait);
        printer.setPaperSize(QPrinter::A4);
        printer.setPageMargins(15,15,15,15,QPrinter::Millimeter);
        printer.setFullPage(true);

        // force printer page size to be used:
        QSize pageSize = printer.pageRect().size();
        widget.resize( pageSize );
    }

    ui.header->setFrameShape( QFrame::Shape::Box );

    QHBoxLayout* headerLayout = new QHBoxLayout(ui.header);

    QLabel* icon = new QLabel(ui.header);
    QSize size = ui.header->size();
    icon->setPixmap( QPixmap( ":/gui_test/mainframe.png" ).scaledToHeight( size.height() ) );

    headerLayout->addWidget( icon );
    headerLayout->addWidget( new QLabel("Document title",ui.header) );

    headerLayout->setStretch( 0, 0 );
    headerLayout->setStretch( 1, 1 );

    ui.inputs->setText( "<b>Info</b>: Information" );

    QwtPlot* plot = new QwtPlot( &widget );

    QwtPlotCurve* curve = new QwtPlotCurve("Plots");
    curve->setStyle( QwtPlotCurve::Lines );

    QVector<QPointF> samples;
    for ( size_t i = 0; i != 100; ++i )
    {
        samples.push_back(QPointF(i,20*i+10));
    }
    curve->setData(new QwtPointSeriesData(samples));

    curve->attach(plot);
    plot->setTitle( "Result" );
    plot->setAxisScale( QwtPlot::xBottom, samples.front().rx(), samples.back().rx() );

    plot->replot();

    ui.graphLayout->addWidget( plot );

    if ( printWidget( widget, printHighResolution, fileName ) )
    {
        QDesktopServices::openUrl(QUrl::fromLocalFile(fileName.c_str()));
        return true;
    }
    else
    {
        return false;
    }
}

int main( int argc, char* argv[] )
{
    QApplication app( argc, argv );
    app.setWindowIcon( QIcon( ":/gui_test/mainframe.png" ) );

    generateReport( false, false, "report_small_widget_to_screen.pdf" );
    generateReport( false, true, "report_small_widget_to_high.pdf" );
    generateReport( true, false, "report_big_widget_to_screen.pdf" );
    generateReport( true, true, "report_big_widget_to_high.pdf" );

    return 0;
}

report.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Report</class>
 <widget class="QWidget" name="Report">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>525</width>
    <height>742</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Form</string>
  </property>
  <layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,100,0">
   <item>
    <widget class="QFrame" name="header"/>
   </item>
   <item>
    <widget class="QLabel" name="inputs">
     <property name="text">
      <string>TextLabel</string>
     </property>
    </widget>
   </item>
   <item>
    <spacer name="verticalSpacer">
     <property name="orientation">
      <enum>Qt::Vertical</enum>
     </property>
     <property name="sizeHint" stdset="0">
      <size>
       <width>20</width>
       <height>40</height>
      </size>
     </property>
    </spacer>
   </item>
   <item>
    <layout class="QVBoxLayout" name="graphLayout"/>
   </item>
   <item>
    <widget class="QWidget" name="footer" native="true"/>
   </item>
  </layout>
 </widget>
 <resources/>
 <connections/>
</ui>

mainframe.png: A Qt icon of 256x256 pixels: http://icons.iconarchive.com/icons/alecive/flatwoken/256/Apps-Qt-icon.png.

As you can see, this generates 4 files:

  • report_small_widget_to_screen.pdf: Where widget is created with a small A4 ratio from ui file (252x742) and then printed with QPrinter::ScreenResolution
  • report_small_widget_to_high.pdf: Where widget is created with a small A4 ratio from ui file (252x742) and then printed with QPrinter::HighResolution
  • report_big_widget_to_screen.pdf: Where widget is scaled to printer's page size (793x1123) and then printed with QPrinter::ScreenResolution
  • report_big_widget_to_high.pdf: Where widget is scaled to printer's page size (9917x14033) and then printed with QPrinter::HighResolution

None gives me an acceptable result:

  • report_small_widget_to_screen.pdf: Text, Qt icon and Qwt chart are pixelated
  • report_small_widget_to_high.pdf: Text is OK, but Qt icon and Qwt chart are pixelated
  • report_big_widget_to_screen.pdf: Text, Qt icon and Qwt chart are pixelated
  • report_big_widget_to_high.pdf: Text, Qt icon and Qwt chart are so small they are hardly readable. But now Qwt plot is not pixelated anymore

What should I change to get a nice output?

  • With text drawn in high resolution (not pixelated) as in report_small_widget_to_high.pdf
  • With icon being drawn with high resolution (not pixelated)
  • With chart being plotted with high resolution (not pixelated)

report_small_widget_to_screen.pdf looks like (everything is pixelated):

report_small_widget_to_high.pdf looks like (only text is not pixelated):

report_big_widget_to_high.pdf looks like (everything is too small):


Note: I just ran the same tests with a bigger .ui ressource (2100x2970), then image resolution looks better, but text appears very small. I'm wondering the QWidget printing is the appropriate solution here, because it looks like text size depends on ui size, so you can't control the size of your text (as you do with font sizes in a document like Word...)


回答1:


Personally I would strongly consider creating the content as a QTextDocument and then printing it. You can (IIRC, haven't used QwtPlot in a while) render the charts to images. Then it is easy to add the image to the document wherever you want. Same with your static image(s), of course. You can use HTML/CSS in the document (despite "Text" in the class name), and lots of other options.

Example added on request:

This is going to be very simplified and not fully formed, but hope it helps...

QTextDocument qtdoc;  // start with a QTextDocument

// prepare standard html with embedded image
QString html = "<html><body>" \
    "<h1>Hello World!</h1>" \
    "<img src='mydata://myimg.png' border='0' />" \
"</body></html>";  

QImage image = getChartImage();  // theoretical function

// here we add the actual image data to the document
qtdoc.addResource(QTextDocument::ImageResource, QUrl("mydata://myimg.png"), image);

qtdoc.setHtml(html);
// document is now fully formed and ready for display/print

To print to a PDF or HTML file:

void printToFile(const QTextDocument & qtdoc)
{
  QString fn = QFileDialog::getSaveFileName(this, tr("Select output file"), QString(), tr("PDF Files(*.pdf);;HTML-Files (*.htm *.html)"));
  if (fn.isEmpty())
    return;
  if (fn.endsWith(".pdf", Qt::CaseInsensitive)) {
    QPrinter printer;
    printer.setPageMargins(10.0,10.0,10.0,10.0,printer.Millimeter);
    printer.setOutputFormat(QPrinter::PdfFormat);
    printer.setColorMode(QPrinter::Color);
    printer.setOutputFileName(fn);
    qtdoc.print(&printer);
  }
  else {  // HTML
    QTextDocumentWriter writer(fn);
    writer.write(qtdoc);
  }
}

Output to a printer:

void print(const QTextDocument & qtdoc)
{
  QPrinter printer;
  printer.setPageMargins(10.0,10.0,10.0,10.0,printer.Millimeter);
  QPrintDialog *dialog = new QPrintDialog(&printer, this);
  dialog->setWindowTitle(tr("Print Document"));
  if (dialog->exec() != QDialog::Accepted)
    return;
  qtdoc.print(&printer);
}

An actual screenshot of a working version (code for it starts here but is a bit of a chore to follow):

And the PDF export: http://max.wdg.us/docs/so/SO-47879329.pdf



来源:https://stackoverflow.com/questions/47870408/cant-generate-pdf-with-acceptable-output-quality-using-qt

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!