Skip to content

Commit

Permalink
FIXED/REWORKED issue #111 : Can't write to PDF files with JKQTPlotter…
Browse files Browse the repository at this point in the history
…::saveImage() when passing a filename ending in ".pdf" (thanks to https://github.com/fpalazzolo for reporting)

IMPROVED/REWORKED: The functions JKQTBasePlotter::saveImage(), JKQTBasePlotter::saveAsPixelImage(), JKQTBasePlotter::saveAsPDF(), JKQTBasePlotter::saveSVG(), ... gained a bool return value to indicate whether sacing was successful.
IMPROVED/REWORKED: More <code>save...()</code> functions will appear in the API of JKQTPlotter, so you don't have to go via JKQTPlotter::getPlotter(). These are merely forwarding the call to the internel JKQTBasePlotter instance.
  • Loading branch information
jkriege2 committed Dec 21, 2023
1 parent abad0b0 commit 995ca92
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 216 deletions.
3 changes: 3 additions & 0 deletions doc/dox/whatsnew.dox
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
<li>FIXED issue <a href="https://github.com/jkriege2/JKQtPlotter/pull/99">#99: Height of one-column key/legend was too large</a> (thanks to <a href="https://github.com/allenbarnett5">user:allenbarnett5/a> for reporting)</li>
<li>FIXED/IMPROVED issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/100">#100: Add option to disable resize delay feature by setting the delay to zero</a> (thanks to <a href="https://github.com/fpalazzolo">user:fpalazzolo</a> for reporting)</li>
<li>FIXED/NEW: placement of plot-title (was not centerd in its box, but glued to the bottom) by adding a plotstyle parameter JKQTBasePlotterStyle::plotLabelOffset</li>
<li>FIXED/REWORKED issue <a href="https://github.com/jkriege2/JKQtPlotter/issues/111">#111: Can't write to PDF files with JKQTPlotter::saveImage() when passing a filename ending in ".pdf"</a> (thanks to <a href="https://github.com/fpalazzolo">user:fpalazzolo/a> for reporting):<br/>While fixing this issue, the functions JKQTBasePlotter::saveImage() etc. gained a bool return value to indicate whether sacing was successful.</li>
<li>REORGANIZED: separated line-graphs from jkqtpscatter.h/.cpp into jkqtplines.h/.cpp</li>
<li>IMPROVED: QT6-compatibility by removing deprecated warnings</li>
<li>IMPROVED: added missing override declarations</li>
Expand All @@ -57,6 +58,8 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
<li>IMPROVED/REWORKED: zomm/pan by mouse-wheel: now there are modes that support zoomin AND panning by trakpad and mouse-wheel simultaneously! This can only be implemented using heuristics, due to the way that Qt handles track-pad events, but the current solution should at least improve the behaviour seen before. Mainly <code>jkqtpmwaZoomByWheelAndTrackpadPan</code> was introduced into <code>JKQTPMouseWheelActions</code> und is set as default mode: Here JKQTPlotter tries to distinguish the QWheelEvent s sent by an actual mouse wheel and a trackpad.</li>
<li>IMPROVED/REWORKED: better example graphs in \link JKQTPlotterStyling.</li>
<li>IMPROVED/REWORKED: legend/key positioning as combination of 3 values, e.g. \c JKQTPKeyOutsideTop|JKQTPKeyTop|JKQTPKeyRight or \c JKQTPKeyInside|JKQTPKeyTopJKQTPKeyRight</li>
<li>IMPROVED/REWORKED: The functions JKQTBasePlotter::saveImage(), JKQTBasePlotter::saveAsPixelImage(), JKQTBasePlotter::saveAsPDF(), JKQTBasePlotter::saveSVG(), ... gained a bool return value to indicate whether sacing was successful.</li>
<li>IMPROVED/REWORKED: More <code>save...()</code> functions will appear in the API of JKQTPlotter, so you don't have to go via JKQTPlotter::getPlotter(). These are merely forwarding the call to the internel JKQTBasePlotter instance.</li>
<li>IMPROVED: documentation of styles: automatized doc image generation.</li>
<li>NEW: JKQTPFilledCurveXGraph and JKQTPFilledCurveYGraph can now plot wiggle plots with different fill styles above and below the baseline (feature request <a href="https://github.com/jkriege2/JKQtPlotter/issues/68">#68 Wiggle Plots</a> from <a href="https://github.com/xichaoqiang">user:xichaoqiang</a> </li>
<li>NEW/BREAKING CHANGE: data tooltip can now also be shown when "just" moving the mouse (so far this was only possible when dragging the mouse with a button pressed). This also removes JKQtPlotter::getActMouseLeftAsToolTip() and adds JKQtPlotter::getActMouseMoveToolTip() instead! Also the default toolbars and context menus changed!</li>
Expand Down
218 changes: 132 additions & 86 deletions lib/jkqtplotter/jkqtpbaseplotter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3501,7 +3501,7 @@ void JKQTBasePlotter::saveAsGerExcelCSV(const QString& filename) {
}

#ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT
void JKQTBasePlotter::saveAsPDF(const QString& filename, bool displayPreview) {
bool JKQTBasePlotter::saveAsPDF(const QString& filename, bool displayPreview) {
loadUserSettings();
QString fn=filename;
if (fn.isEmpty()) {
Expand All @@ -3513,7 +3513,7 @@ void JKQTBasePlotter::saveAsPDF(const QString& filename, bool displayPreview) {

if (!fn.isEmpty()) {
emit beforeExporting();; auto __finalpaint=JKQTPFinally([&]() { emit afterExporting();});
QPrinter* printer=new QPrinter;
std::shared_ptr<QPrinter> printer=std::make_shared<QPrinter>();
bool doLandscape=widgetWidth>widgetHeight;
if (gridPrinting) {
gridPrintingCalc();
Expand All @@ -3529,127 +3529,167 @@ void JKQTBasePlotter::saveAsPDF(const QString& filename, bool displayPreview) {
printer->setOutputFileName(fn);
printer->setPageMargins(QMarginsF(0,0,0,0),QPageLayout::Millimeter);
printer->setColorMode(QPrinter::Color);
printpreviewNew(printer, true, -1.0, -1.0, displayPreview);
delete printer;
return printpreviewNew(printer.get(), true, -1.0, -1.0, displayPreview);
}
saveUserSettings();
return false;
}
#endif

void JKQTBasePlotter::saveImage(const QString& filename, bool displayPreview) {
bool JKQTBasePlotter::saveImage(const QString& filename, bool displayPreview) {
loadUserSettings();
QString fn=filename;
QStringList filt;
QStringList filterstrings;
QList<QStringList> filterextensions;

const auto findExporterByExtension=[&filterextensions](const QString& ext) {
const QString extl=ext.toLower();
for (int i=0; i<filterextensions.size(); i++) {
if (filterextensions[i].contains(extl)) return i;
}
return -1;
};

// add default exporters
#ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT
filt<<tr("Portable Document Format PDF [Qt] (*.pdf)");
filt<<tr("Scalable Vector Graphics [Qt] (*.svg)");
filterstrings<<tr("Portable Document Format PDF [Qt] (*.pdf)"); filterextensions<<(QStringList()<<"pdf");
const int idxDefaultPDF=filterstrings.size()-1;
filterstrings<<tr("Scalable Vector Graphics [Qt] (*.svg)"); filterextensions<<(QStringList()<<"svg");
const int idxDefaultSVG=filterstrings.size()-1;
#endif
filt<<tr("PNG Image [Qt] (*.png)");
filt<<tr("BMP Image [Qt] (*.bmp)");
filt<<tr("TIFF Image [Qt] (*.tif *.tiff)");
filt<<tr("JPEG Image [Qt] (*.jpg *.jpeg)");
const int filtStartSize=filt.size();
filterstrings<<tr("PNG Image [Qt] (*.png)"); filterextensions<<(QStringList()<<"png");
filterstrings<<tr("BMP Image [Qt] (*.bmp)"); filterextensions<<(QStringList()<<"bmp");
filterstrings<<tr("TIFF Image [Qt] (*.tif *.tiff)"); filterextensions<<(QStringList()<<"tif"<<"tiff");
filterstrings<<tr("JPEG Image [Qt] (*.jpg *.jpeg)"); filterextensions<<(QStringList()<<"jpg"<<"jpeg");
// add JKQTPPaintDeviceAdapter exporters
const int filtersIndexFirstExporterPLugin=filterstrings.size();
{
JKQTPSynchronized<QList<JKQTPPaintDeviceAdapter*>>::Locker lock(jkqtpPaintDeviceAdapters);
for (int i=0; i<jkqtpPaintDeviceAdapters.get().size(); i++) {
filt<<jkqtpPaintDeviceAdapters.get()[i]->getFilter();
filterstrings<<jkqtpPaintDeviceAdapters.get()[i]->getFilter();
filterextensions<<QStringList();
for (const auto& ext: jkqtpPaintDeviceAdapters.get()[i]->getFileExtension()) filterextensions.last()<<ext.toLower();
}
}
int qtwritersidx=filt.size();
QList<QByteArray> writerformats=QImageWriter::supportedImageFormats();
const bool isWithSpecialDeviceAdapter=(filterstrings.size()>filtersIndexFirstExporterPLugin);
// add remaining QImageWriter exporters
const int filtersIndexFirstQtWriter=filterstrings.size();
const QList<QByteArray> writerformats=QImageWriter::supportedImageFormats();
for (int i=0; i<writerformats.size(); i++) {
filt<<QString("%1 Image (*.%2)").arg(QString(writerformats[i]).toUpper()).arg(QString(writerformats[i].toLower()));
const QString ext=writerformats[i].toLower();
const int extIdx=findExporterByExtension(ext);
const QString name=writerformats[i].toUpper();
// only add QtWriters that are not yt contained in the default filters options
if (extIdx<0 || extIdx>=filtersIndexFirstExporterPLugin) {
filterstrings<<QString("%1 Image (*.%2)").arg(name).arg(ext);
filterextensions<<(QStringList()<<ext);
}
}
QString selFormat="";

QString selFormat;
if (fn.isEmpty()) {
selFormat=currentFileFormat;
fn = QFileDialog::getSaveFileName(nullptr, tr("Save Plot"),
currentSaveDirectory,
filt.join(";;"), &selFormat);
filterstrings.join(";;"), &selFormat);
if (!fn.isEmpty()) {
currentSaveDirectory=QFileInfo(fn).absolutePath();
currentFileFormat=selFormat;
}
} else {
const QString fnExt=QFileInfo(filename).suffix().toLower();
const int filtidx=findExporterByExtension(fnExt);
if (filtidx>=0) selFormat=filterstrings[filtidx];

}

//qDebug()<<"fn="<<fn<<" selFormat="<<selFormat;

saveUserSettings();
if (!fn.isEmpty()) {
int filtID=filt.indexOf(selFormat);
const QString fnExt=QFileInfo(filename).suffix().toLower();

const int filtID=[&](){
// 1. look for the selected format
int idx=filterstrings.indexOf(selFormat);
// 2. if not found try to match up the file extension
if (idx<0) idx=findExporterByExtension(fnExt);
return idx;
}();
JKQTPSynchronized<QList<JKQTPPaintDeviceAdapter*>>::Locker lock(jkqtpPaintDeviceAdapters);

bool isWithSpecialDeviceAdapter=filtID>=filtStartSize && filtID<filtStartSize+jkqtpPaintDeviceAdapters.get().size();
int adapterID=filtID-filtStartSize;
QString e=QFileInfo(filename).suffix().toLower();
if (!isWithSpecialDeviceAdapter) {
for (int i=0; i<jkqtpPaintDeviceAdapters.get().size(); i++) {
if (jkqtpPaintDeviceAdapters.get()[i]->getFileExtension().contains(e)) {
adapterID=i;
isWithSpecialDeviceAdapter=true;
break;
}
}
// now we determine whether we selected a jkqtpPaintDeviceAdapters, if not adapterID will be <0
const int adapterID=[&](){
int idx=filtID-filtersIndexFirstExporterPLugin;
if (idx<0) idx=-1;
if (idx>=jkqtpPaintDeviceAdapters.get().size()) idx=-1;
return idx;
}();


if (filtID<0) {
qWarning()<<"You tried to save an image to to '"<<fn<<"', but JKQTPlottter did not recognize the file format!";
return false;
}
//qDebug()<<"filtID="<<filtID<<" isWithSpecialDeviceAdapter="<<isWithSpecialDeviceAdapter<<" adapterID="<<adapterID;


#ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT
if (filtID==0) {
saveAsPDF(fn, displayPreview);
return;
// SVG and PDF need to be treated separately!
if (filtID==idxDefaultPDF) {
return saveAsPDF(fn, displayPreview);
}
if (filtID==1) {
saveAsSVG(fn, displayPreview);
return;
if (filtID==idxDefaultSVG) {
return saveAsSVG(fn, displayPreview);
}
#endif
if (isWithSpecialDeviceAdapter && adapterID>=0 && adapterID<jkqtpPaintDeviceAdapters.get().size()) {

// we need to use a jkqtpPaintDeviceAdapters
if (adapterID>=0) {
QString tempFM="";
if (QFile::exists(fn)) {
// if the file fn already exists, we make a temporary copy, so it is not destroyed by the export process!
#ifdef QFWIDLIB_LIBRARY
QFTemporaryFile* tf=new QFTemporaryFile();
QSharedPointer<QFTemporaryFile> tf=QSharedPointer<QTemporaryFile>(new QFTemporaryFile());
#else
QTemporaryFile* tf=new QTemporaryFile();
QSharedPointer<QTemporaryFile> tf=QSharedPointer<QTemporaryFile>(new QTemporaryFile());
#endif
tf->open();
tempFM=tf->fileName();
tf->close();
delete tf;
tf.reset();
QFile::copy(fn, tempFM);
}


emit beforeExporting();; auto __finalpaint=JKQTPFinally([&]() { emit afterExporting();});

gridPrintingCalc();
QPaintDevice* paintDevice=jkqtpPaintDeviceAdapters.get()[adapterID]->createPaintdevice(fn, jkqtp_roundTo<int>(gridPrintingSize.width()), jkqtp_roundTo<int>(gridPrintingSize.height()));
QSharedPointer<QPaintDevice> paintDevice=QSharedPointer<QPaintDevice>(jkqtpPaintDeviceAdapters.get()[adapterID]->createPaintdevice(fn, jkqtp_roundTo<int>(gridPrintingSize.width()), jkqtp_roundTo<int>(gridPrintingSize.height())));

#ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT
if (!printpreviewNew(paintDevice, jkqtpPaintDeviceAdapters.get()[adapterID]->getSetAbsolutePaperSize(), jkqtpPaintDeviceAdapters.get()[adapterID]->getPrintSizeXInMM(), jkqtpPaintDeviceAdapters.get()[adapterID]->getPrintSizeYInMM(), displayPreview)) {
delete paintDevice;


if (!printpreviewNew(paintDevice.get(), jkqtpPaintDeviceAdapters.get()[adapterID]->getSetAbsolutePaperSize(), jkqtpPaintDeviceAdapters.get()[adapterID]->getPrintSizeXInMM(), jkqtpPaintDeviceAdapters.get()[adapterID]->getPrintSizeYInMM(), displayPreview)) {
if (QFile::exists(tempFM)) {
QFile::copy(tempFM, fn);
QFile::remove(tempFM);
}
} else {
#else
{
return false;
} else
#endif
delete paintDevice;
paintDevice=jkqtpPaintDeviceAdapters.get()[adapterID]->createPaintdeviceMM(fn,printSizeX_Millimeter,printSizeY_Millimeter);
printpreviewPaintRequestedNewPaintDevice(paintDevice);
delete paintDevice;
{
paintDevice.reset(jkqtpPaintDeviceAdapters.get()[adapterID]->createPaintdeviceMM(fn,printSizeX_Millimeter,printSizeY_Millimeter));
printpreviewPaintRequestedNewPaintDevice(paintDevice.get());
return true;
}

} else {
saveAsPixelImage(fn, displayPreview, writerformats.value(filtID-qtwritersidx, QByteArray()));
// here we can let Qt figure out the correct exporter
return saveAsPixelImage(fn, displayPreview, writerformats.value(filtID-filtersIndexFirstQtWriter, QByteArray()));
}
}
return false;
}


void JKQTBasePlotter::saveAsPixelImage(const QString& filename, bool displayPreview, const QByteArray& outputFormat, const QSize &outputSizeIncrease) {
bool JKQTBasePlotter::saveAsPixelImage(const QString& filename, bool displayPreview, const QByteArray& outputFormat, const QSize &outputSizeIncrease) {
loadUserSettings();
QString fn=filename;
QStringList filt;
Expand All @@ -3668,15 +3708,17 @@ void JKQTBasePlotter::saveAsPixelImage(const QString& filename, bool displayPrev

saveUserSettings();
if (!fn.isEmpty()) {
int filtID=filt.indexOf(selFormat);
const int filtID=filt.indexOf(selFormat);
//QString ext=tolower(extract_file_ext(fn.toStdString()));
QString form="NONE";
if (filtID>=0 && filtID<writerformats.size()) {
form=writerformats[filtID];
}
if (outputFormat.size()>0) {
form =outputFormat;
}
const QString form=[&]()->QString{
if (filtID>=0 && filtID<writerformats.size()) {
return writerformats[filtID];
}
if (outputFormat.size()>0) {
return outputFormat;
}
return "NONE";
}();


emit beforeExporting();; auto __finalpaint=JKQTPFinally([&]() { emit afterExporting();});
Expand All @@ -3692,26 +3734,29 @@ void JKQTBasePlotter::saveAsPixelImage(const QString& filename, bool displayPrev

QImage png(QSizeF(double(printSizeX_Millimeter)+outputSizeIncrease.width(), double(printSizeY_Millimeter)+outputSizeIncrease.height()).toSize(), QImage::Format_ARGB32);
png.fill(Qt::transparent);
JKQTPEnhancedPainter painter;
painter.begin(&png);
painter.setRenderHint(JKQTPEnhancedPainter::Antialiasing);
painter.setRenderHint(JKQTPEnhancedPainter::TextAntialiasing);
painter.setRenderHint(JKQTPEnhancedPainter::SmoothPixmapTransform);
#if (QT_VERSION<QT_VERSION_CHECK(6, 0, 0))
painter.setRenderHint(JKQTPEnhancedPainter::NonCosmeticDefaultPen, true);
painter.setRenderHint(JKQTPEnhancedPainter::HighQualityAntialiasing);
#endif
{
JKQTPEnhancedPainter painter;
painter.begin(&png);
painter.setRenderHint(JKQTPEnhancedPainter::Antialiasing);
painter.setRenderHint(JKQTPEnhancedPainter::TextAntialiasing);
painter.setRenderHint(JKQTPEnhancedPainter::SmoothPixmapTransform);
#if (QT_VERSION<QT_VERSION_CHECK(6, 0, 0))
painter.setRenderHint(JKQTPEnhancedPainter::NonCosmeticDefaultPen, true);
painter.setRenderHint(JKQTPEnhancedPainter::HighQualityAntialiasing);
#endif

/*calcPlotScaling(painter);
gridPaint(painter, png.rect().size());*/\
//qDebug()<<QSize(printSizeX_Millimeter, printSizeY_Millimeter);
exportpreviewPaintRequested(painter, QSize(jkqtp_roundTo<int>(printSizeX_Millimeter), jkqtp_roundTo<int>(printSizeY_Millimeter)));
painter.end();
if (form=="NONE") png.save(fn);
else png.save(fn, form.toLatin1().data());
/*calcPlotScaling(painter);
gridPaint(painter, png.rect().size());*/\
//qDebug()<<QSize(printSizeX_Millimeter, printSizeY_Millimeter);
exportpreviewPaintRequested(painter, QSize(jkqtp_roundTo<int>(printSizeX_Millimeter), jkqtp_roundTo<int>(printSizeY_Millimeter)));
painter.end();
}
if (form=="NONE") return png.save(fn);
else return png.save(fn, form.toLatin1().data());
}

}
return false;
}

QImage JKQTBasePlotter::grabPixelImage(QSize size, bool showPreview)
Expand Down Expand Up @@ -3843,7 +3888,8 @@ void JKQTBasePlotter::copyPixelImage(bool showPreview) {
}

#ifndef JKQTPLOTTER_COMPILE_WITHOUT_PRINTSUPPORT
void JKQTBasePlotter::saveAsSVG(const QString& filename, bool displayPreview) {
bool JKQTBasePlotter::saveAsSVG(const QString& filename, bool displayPreview) {
bool printed=false;
loadUserSettings();
QString fn=filename;
if (fn.isEmpty()) {
Expand All @@ -3870,23 +3916,23 @@ void JKQTBasePlotter::saveAsSVG(const QString& filename, bool displayPreview) {

emit beforeExporting();; auto __finalpaint=JKQTPFinally([&]() { emit afterExporting();});
gridPrintingCalc();
QSvgGenerator* svg=new QSvgGenerator;
std::shared_ptr<QSvgGenerator> svg=std::make_shared<QSvgGenerator>();
svg->setResolution(96);
QSize size=QSizeF(gridPrintingSize.width()*25.4/svg->resolution(), gridPrintingSize.height()*25.4/svg->resolution()).toSize();
svg->setSize(size);
svg->setFileName(fn);

if (!printpreviewNew(svg, true, -1.0, -1.0, displayPreview)) {
printed=printpreviewNew(svg.get(), true, -1.0, -1.0, displayPreview);
if (!printed) {
if (QFile::exists(tempFM)) {
QFile::copy(tempFM, fn);
QFile::remove(tempFM);
}
}

delete svg;

}
saveUserSettings();
return printed;
}
#endif

Expand Down
Loading

0 comments on commit 995ca92

Please sign in to comment.