1 #include "pdfexport.h"
2 #include "common/unused.h"
3 #include "uiutils.h"
4 #include "log.h"
5 #include <QtMath>
6 #include <QPainter>
7 #include <QFont>
8 #include <QDebug>
9 
10 QString PdfExport::bulletChar = "\u2022";
11 
init()12 bool PdfExport::init()
13 {
14     textOption = new QTextOption();
15     textOption->setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
16     return GenericExportPlugin::init();
17 }
18 
deinit()19 void PdfExport::deinit()
20 {
21     safe_delete(textOption);
22 }
23 
createPaintDevice(const QString & documentTitle,bool & takeOwnership)24 QPagedPaintDevice* PdfExport::createPaintDevice(const QString& documentTitle, bool &takeOwnership)
25 {
26     QPdfWriter* pdfWriter = new QPdfWriter(output);
27     pdfWriter->setTitle(documentTitle);
28     pdfWriter->setCreator(tr("SQLiteStudio v%1").arg(SQLITESTUDIO->getVersionString()));
29     takeOwnership = true;
30     return pdfWriter;
31 }
32 
getFormatName() const33 QString PdfExport::getFormatName() const
34 {
35     return "PDF";
36 }
37 
standardOptionsToEnable() const38 ExportManager::StandardConfigFlags PdfExport::standardOptionsToEnable() const
39 {
40     return ExportManager::StandardConfigFlags();
41 }
42 
getProviderFlags() const43 ExportManager::ExportProviderFlags PdfExport::getProviderFlags() const
44 {
45     return ExportManager::DATA_LENGTHS|ExportManager::ROW_COUNT;
46 }
47 
validateOptions()48 void PdfExport::validateOptions()
49 {
50 }
51 
defaultFileExtension() const52 QString PdfExport::defaultFileExtension() const
53 {
54     return "pdf";
55 }
56 
beforeExportQueryResults(const QString & query,QList<QueryExecutor::ResultColumnPtr> & columns,const QHash<ExportManager::ExportProviderFlag,QVariant> providedData)57 bool PdfExport::beforeExportQueryResults(const QString& query, QList<QueryExecutor::ResultColumnPtr>& columns, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData)
58 {
59     UNUSED(query);
60 
61     if (!beginDoc(tr("SQL query results")))
62         return false;
63 
64     totalRows = providedData[ExportManager::ROW_COUNT].toInt();
65 
66     QStringList columnNames;
67     for (const QueryExecutor::ResultColumnPtr& col : columns)
68         columnNames << col->displayName;
69 
70     clearDataHeaders();
71     exportDataColumnsHeader(columnNames);
72 
73     QList<int> columnDataLengths = getColumnDataLengths(columnNames.size(), providedData);
74     calculateDataColumnWidths(columnNames, columnDataLengths);
75     return true;
76 }
77 
exportQueryResultsRow(SqlResultsRowPtr row)78 bool PdfExport::exportQueryResultsRow(SqlResultsRowPtr row)
79 {
80     exportDataRow(row->valueList());
81     return true;
82 }
83 
exportTable(const QString & database,const QString & table,const QStringList & columnNames,const QString & ddl,SqliteCreateTablePtr createTable,const QHash<ExportManager::ExportProviderFlag,QVariant> providedData)84 bool PdfExport::exportTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateTablePtr createTable, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData)
85 {
86     UNUSED(columnNames);
87     UNUSED(database);
88     UNUSED(ddl);
89 
90     if (isTableExport() && !beginDoc(tr("Exported table: %1").arg(table)))
91         return false;
92 
93     exportObjectHeader(tr("Table: %1").arg(table));
94 
95     QStringList tableDdlColumns = {tr("Column"), tr("Data type"), tr("Constraints")};
96     exportObjectColumnsHeader(tableDdlColumns);
97 
98     QString colDef;
99     QString colType;
100     QStringList columnsAndTypes;
101     int colNamesLength = 0;
102     int dataTypeLength = 0;
103     for (SqliteCreateTable::Column* col : createTable->columns)
104     {
105         colDef = col->name;
106         colNamesLength = qMax(colNamesLength, colDef.size());
107         colType = "";
108         if (col->type)
109         {
110             colType = col->type->toDataType().toFullTypeString();
111             colDef += "\n" + colType;
112             dataTypeLength = qMax(dataTypeLength, colType.size());
113         }
114 
115         columnsAndTypes << colDef;
116     }
117 
118     QList<int> columnDataLengths = {colNamesLength, dataTypeLength, 0};
119     calculateDataColumnWidths(tableDdlColumns, columnDataLengths, 2);
120 
121     for (SqliteCreateTable::Column* col : createTable->columns)
122         exportTableColumnRow(col);
123 
124     if (createTable->constraints.size() > 0)
125     {
126         QStringList tableDdlColumns = {tr("Global table constraints")};
127         exportObjectColumnsHeader(tableDdlColumns);
128         exportTableConstraintsRow(createTable->constraints);
129     }
130 
131     flushObjectPages();
132 
133     prepareTableDataExport(table, columnsAndTypes, providedData);
134     return true;
135 }
136 
exportVirtualTable(const QString & database,const QString & table,const QStringList & columnNames,const QString & ddl,SqliteCreateVirtualTablePtr createTable,const QHash<ExportManager::ExportProviderFlag,QVariant> providedData)137 bool PdfExport::exportVirtualTable(const QString& database, const QString& table, const QStringList& columnNames, const QString& ddl, SqliteCreateVirtualTablePtr createTable, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData)
138 {
139     UNUSED(columnNames);
140     UNUSED(database);
141     UNUSED(ddl);
142     UNUSED(createTable);
143 
144     if (isTableExport() && !beginDoc(tr("Exported table: %1").arg(table)))
145         return false;
146 
147     prepareTableDataExport(table, columnNames, providedData);
148     return true;
149 }
150 
prepareTableDataExport(const QString & table,const QStringList & columnNames,const QHash<ExportManager::ExportProviderFlag,QVariant> providedData)151 void PdfExport::prepareTableDataExport(const QString& table, const QStringList& columnNames, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData)
152 {
153     resetDataTable();
154     totalRows = providedData[ExportManager::ROW_COUNT].toInt();
155 
156     // Prepare for exporting data row
157     clearDataHeaders();
158     if (!isTableExport()) // for database export we need to mark what is this object name
159         exportDataHeader(tr("Table: %1").arg(table));
160 
161     exportDataColumnsHeader(columnNames);
162 
163     QList<int> columnDataLengths = getColumnDataLengths(columnNames.size(), providedData);
164     calculateDataColumnWidths(columnNames, columnDataLengths);
165 }
166 
getColumnDataLengths(int columnCount,const QHash<ExportManager::ExportProviderFlag,QVariant> providedData)167 QList<int> PdfExport::getColumnDataLengths(int columnCount, const QHash<ExportManager::ExportProviderFlag, QVariant> providedData)
168 {
169     QList<int> columnDataLengths = providedData[ExportManager::DATA_LENGTHS].value<QList<int>>();
170     if (columnDataLengths.size() < columnCount)
171     {
172         qWarning() << "PdfExport: column widths provided by ExportWorker (" << columnDataLengths.size()
173                    << ") is less than number of columns to export (" << columnCount << ").";
174     }
175 
176     // Fill up column data widths if there are any missing from the provided data (should not happen)
177     while (columnDataLengths.size() < columnCount)
178         columnDataLengths << maxColWidth;
179 
180     for (int& val : columnDataLengths)
181     {
182         if (val > cellDataLimit)
183             val = cellDataLimit;
184     }
185 
186     return columnDataLengths;
187 }
188 
exportTableRow(SqlResultsRowPtr data)189 bool PdfExport::exportTableRow(SqlResultsRowPtr data)
190 {
191     exportDataRow(data->valueList());
192     return true;
193 }
194 
afterExport()195 bool PdfExport::afterExport()
196 {
197     endDoc();
198     return true;
199 }
200 
afterExportTable()201 bool PdfExport::afterExportTable()
202 {
203     flushDataPages(true);
204     return true;
205 }
206 
afterExportQueryResults()207 bool PdfExport::afterExportQueryResults()
208 {
209     flushDataPages(true);
210     return true;
211 }
212 
beforeExportDatabase(const QString & database)213 bool PdfExport::beforeExportDatabase(const QString& database)
214 {
215     return beginDoc(tr("Exported database: %1").arg(database));
216 }
217 
exportIndex(const QString & database,const QString & name,const QString & ddl,SqliteCreateIndexPtr createIndex)218 bool PdfExport::exportIndex(const QString& database, const QString& name, const QString& ddl, SqliteCreateIndexPtr createIndex)
219 {
220     UNUSED(database);
221     UNUSED(ddl);
222 
223     exportObjectHeader(tr("Index: %1").arg(name));
224 
225     QStringList indexColumns = {tr("Property", "index header"), tr("Value", "index header")};
226     exportObjectColumnsHeader(indexColumns);
227 
228     exportObjectRow({tr("Indexed table"), name});
229     exportObjectRow({tr("Unique index"), (createIndex->uniqueKw ? tr("Yes") : tr("No"))});
230 
231     indexColumns = QStringList({tr("Column"), tr("Collation"), tr("Sort order")});
232     exportObjectColumnsHeader(indexColumns);
233 
234     QString sort;
235     for (SqliteOrderBy* idxCol : createIndex->indexedColumns)
236     {
237         if (idxCol->order != SqliteSortOrder::null)
238             sort = sqliteSortOrder(idxCol->order);
239         else
240             sort = "";
241 
242         exportObjectRow({idxCol->getColumnString(), idxCol->getCollation(), sort});
243     }
244 
245     if (createIndex->where)
246     {
247         indexColumns = QStringList({tr("Partial index condition")});
248         exportObjectColumnsHeader(indexColumns);
249         exportObjectRow(createIndex->where->detokenize());
250     }
251 
252     flushObjectPages();
253     return true;
254 }
255 
exportTrigger(const QString & database,const QString & name,const QString & ddl,SqliteCreateTriggerPtr createTrigger)256 bool PdfExport::exportTrigger(const QString& database, const QString& name, const QString& ddl, SqliteCreateTriggerPtr createTrigger)
257 {
258     UNUSED(database);
259     UNUSED(ddl);
260 
261     exportObjectHeader(tr("Trigger: %1").arg(name));
262 
263     QStringList trigColumns = {tr("Property", "trigger header"), tr("Value", "trigger header")};
264     exportObjectColumnsHeader(trigColumns);
265     exportObjectRow({tr("Activation time"), SqliteCreateTrigger::time(createTrigger->eventTime)});
266 
267     QString event = createTrigger->event ? SqliteCreateTrigger::Event::typeToString(createTrigger->event->type) : "";
268     exportObjectRow({tr("For action"), event});
269 
270     QString onObj;
271     if (createTrigger->eventTime == SqliteCreateTrigger::Time::INSTEAD_OF)
272         onObj = tr("On view");
273     else
274         onObj = tr("On table");
275 
276     exportObjectRow({onObj, createTrigger->table});
277 
278     QString cond = createTrigger->precondition ? createTrigger->precondition->detokenize() : "";
279     exportObjectRow({tr("Activation condition"), cond});
280 
281     QStringList queryStrings;
282     for (SqliteQuery* q : createTrigger->queries)
283         queryStrings << q->detokenize();
284 
285     exportObjectColumnsHeader({tr("Code executed")});
286     exportObjectRow(queryStrings.join("\n"));
287 
288     flushObjectPages();
289     return true;
290 }
291 
exportView(const QString & database,const QString & name,const QString & ddl,SqliteCreateViewPtr view)292 bool PdfExport::exportView(const QString& database, const QString& name, const QString& ddl, SqliteCreateViewPtr view)
293 {
294     UNUSED(database);
295     UNUSED(ddl);
296 
297     exportObjectHeader(tr("View: %1").arg(name));
298     exportObjectColumnsHeader({tr("Query:")});
299     exportObjectRow(view->select->detokenize());
300 
301     flushObjectPages();
302     return true;
303 }
304 
isBinaryData() const305 bool PdfExport::isBinaryData() const
306 {
307     return true;
308 }
309 
beginDoc(const QString & title)310 bool PdfExport::beginDoc(const QString& title)
311 {
312     safe_delete(painter);
313 
314     if (takeDeviceOwnership)
315         safe_delete(pagedWriter);
316 
317     pagedWriter = createPaintDevice(title, takeDeviceOwnership);
318     if (!pagedWriter)
319         return false;
320 
321     painter = new QPainter(pagedWriter);
322     painter->setBrush(Qt::NoBrush);
323     painter->setPen(QPen(Qt::black, lineWidth));
324 
325     setupConfig();
326     return true;
327 }
328 
endDoc()329 void PdfExport::endDoc()
330 {
331     drawFooter();
332 }
333 
cleanupAfterExport()334 void PdfExport::cleanupAfterExport()
335 {
336     safe_delete(painter);
337     if (takeDeviceOwnership)
338         safe_delete(pagedWriter);
339 }
340 
setupConfig()341 void PdfExport::setupConfig()
342 {
343     pagedWriter->setPageSize(convertPageSize(cfg.PdfExport.PageSize.get()));
344     pageWidth = pagedWriter->width();
345     pageHeight = pagedWriter->height();
346     pointsPerMm = pageWidth / pagedWriter->pageSizeMM().width();
347 
348     stdFont = cfg.PdfExport.Font.get();
349     stdFont.setPointSize(cfg.PdfExport.FontSize.get());
350     boldFont = stdFont;
351     boldFont.setBold(true);
352     italicFont = stdFont;
353     italicFont.setItalic(true);
354     painter->setFont(stdFont);
355 
356     topMargin = mmToPoints(cfg.PdfExport.TopMargin.get());
357     rightMargin = mmToPoints(cfg.PdfExport.RightMargin.get());
358     leftMargin = mmToPoints(cfg.PdfExport.LeftMargin.get());
359     bottomMargin = mmToPoints(cfg.PdfExport.BottomMargin.get());
360     updateMargins();
361 
362     maxColWidth = pageWidth / 5;
363     padding = mmToPoints(cfg.PdfExport.Padding.get());
364 
365     QRectF rect = painter->boundingRect(QRectF(padding, padding, pageWidth - 2 * padding, 1), "X", *textOption);
366     minRowHeight = rect.height() + padding * 2;
367     maxRowHeight = qMax((int)(pageHeight * 0.225), minRowHeight);
368     rowsToPrebuffer = (int)qCeil((double)pageHeight / minRowHeight);
369 
370     cellDataLimit = cfg.PdfExport.MaxCellBytes.get();
371     printRowNum = cfg.PdfExport.PrintRowNum.get();
372     printPageNumbers = cfg.PdfExport.PrintPageNumbers.get();
373 
374     lastRowY = getContentsTop();
375     currentPage = -1;
376     rowNum = 1;
377 }
378 
updateMargins()379 void PdfExport::updateMargins()
380 {
381     pageWidth -= (leftMargin + rightMargin);
382     pageHeight -= (topMargin + bottomMargin);
383     painter->setClipRect(QRect(leftMargin, topMargin, pageWidth, pageHeight));
384 
385     if (printPageNumbers)
386     {
387         int pageNumHeight = getPageNumberHeight();
388         bottomMargin += pageNumHeight;
389         pageHeight -= pageNumHeight;
390     }
391 
392     // In order to render full width of the line, we need to add more margin, a half of the line width
393     leftMargin += lineWidth / 2;
394     rightMargin += lineWidth / 2;
395     topMargin += lineWidth / 2;
396     bottomMargin += lineWidth / 2;
397     pageWidth -= lineWidth;
398     pageHeight -= lineWidth;
399 }
400 
clearDataHeaders()401 void PdfExport::clearDataHeaders()
402 {
403     headerRow.reset();
404     columnsHeaderRow.reset();
405 }
406 
resetDataTable()407 void PdfExport::resetDataTable()
408 {
409     clearDataHeaders();
410     bufferedDataRows.clear();
411     rowNum = 0;
412 }
413 
exportDataRow(const QList<QVariant> & data)414 void PdfExport::exportDataRow(const QList<QVariant>& data)
415 {
416     DataCell cell;
417     DataRow row;
418 
419     for (const QVariant& value : data)
420     {
421         switch (value.type())
422         {
423             case QVariant::Int:
424             case QVariant::UInt:
425             case QVariant::LongLong:
426             case QVariant::ULongLong:
427             case QVariant::Double:
428                 cell.alignment = Qt::AlignRight;
429                 break;
430             default:
431                 cell.alignment = Qt::AlignLeft;
432                 break;
433         }
434 
435         if (value.isNull())
436         {
437             cell.alignment = Qt::AlignCenter;
438             cell.isNull = true;
439             cell.contents = QStringLiteral("NULL");
440         }
441         else
442         {
443             cell.isNull = false;
444             cell.contents = value.toString();
445         }
446         row.cells << cell;
447     }
448 
449     bufferedDataRows << row;
450     checkForDataRender();
451 }
452 
exportObjectHeader(const QString & contents)453 void PdfExport::exportObjectHeader(const QString& contents)
454 {
455     ObjectRow row;
456     ObjectCell cell;
457     cell.headerBackground = true;
458     cell.contents << contents;
459     cell.bold = true;
460     cell.alignment = Qt::AlignCenter;
461     row.cells << cell;
462 
463     row.type = ObjectRow::Type::SINGLE;
464     row.recalculateColumnWidths = true;
465     bufferedObjectRows << row;
466 }
467 
exportObjectColumnsHeader(const QStringList & columns)468 void PdfExport::exportObjectColumnsHeader(const QStringList& columns)
469 {
470     ObjectRow row;
471     ObjectCell cell;
472 
473     for (const QString& col : columns)
474     {
475         cell.headerBackground = true;
476         cell.contents.clear();
477         cell.contents << col;
478         cell.alignment = Qt::AlignCenter;
479         row.cells << cell;
480     }
481 
482     row.recalculateColumnWidths = true;
483     row.type = ObjectRow::Type::MULTI;
484     bufferedObjectRows << row;
485 }
486 
exportTableColumnRow(SqliteCreateTable::Column * column)487 void PdfExport::exportTableColumnRow(SqliteCreateTable::Column* column)
488 {
489     ObjectRow row;
490     row.type = ObjectRow::Type::MULTI;
491 
492     ObjectCell cell;
493     cell.contents << column->name;
494     row.cells << cell;
495     cell.contents.clear();
496 
497     if (column->type)
498         cell.contents << column->type->toDataType().toFullTypeString();
499     else
500         cell.contents << "";
501 
502     row.cells << cell;
503     cell.contents.clear();
504 
505     if (column->constraints.size() > 0)
506     {
507         for (SqliteCreateTable::Column::Constraint* constr : column->constraints)
508             cell.contents << constr->detokenize();
509 
510         cell.type = ObjectCell::Type::LIST;
511     }
512     else
513     {
514         cell.contents << "";
515     }
516     row.cells << cell;
517     cell.contents.clear();
518 
519     bufferedObjectRows << row;
520 }
521 
exportTableConstraintsRow(const QList<SqliteCreateTable::Constraint * > & constrList)522 void PdfExport::exportTableConstraintsRow(const QList<SqliteCreateTable::Constraint*>& constrList)
523 {
524     ObjectRow row;
525     row.type = ObjectRow::Type::SINGLE;
526 
527     ObjectCell cell;
528     cell.type = ObjectCell::Type::LIST;
529 
530     if (constrList.size() > 0)
531     {
532         for (SqliteCreateTable::Constraint* constr : constrList)
533             cell.contents << constr->detokenize();
534     }
535     else
536     {
537         cell.contents << "";
538     }
539     row.cells << cell;
540 
541     bufferedObjectRows << row;
542 }
543 
exportObjectRow(const QStringList & values)544 void PdfExport::exportObjectRow(const QStringList& values)
545 {
546     ObjectRow row;
547     row.type = ObjectRow::Type::MULTI;
548 
549     ObjectCell cell;
550     for (const QString& value : values)
551     {
552         cell.contents << value;
553         row.cells << cell;
554         cell.contents.clear();
555     }
556 
557     bufferedObjectRows << row;
558 }
559 
exportObjectRow(const QString & value)560 void PdfExport::exportObjectRow(const QString& value)
561 {
562     ObjectRow row;
563     row.type = ObjectRow::Type::SINGLE;
564 
565     ObjectCell cell;
566     cell.contents << value;
567     row.cells << cell;
568 
569     bufferedObjectRows << row;
570 }
571 
calculateRowHeight(int maxTextWidth,const QStringList & listContents)572 int PdfExport::calculateRowHeight(int maxTextWidth, const QStringList& listContents)
573 {
574     int textWidth = maxTextWidth - calculateBulletPrefixWidth();
575     int totalHeight = 0;
576     for (const QString& contents : listContents)
577         totalHeight += calculateRowHeight(textWidth, contents);
578 
579     return totalHeight;
580 }
581 
calculateBulletPrefixWidth()582 int PdfExport::calculateBulletPrefixWidth()
583 {
584     static QString prefix = bulletChar + " ";
585 
586     QTextOption opt = *textOption;
587     opt.setWrapMode(QTextOption::NoWrap);
588 
589     return painter->boundingRect(QRect(0, 0, 1, 1), prefix, *textOption).width();
590 }
591 
checkForDataRender()592 void PdfExport::checkForDataRender()
593 {
594     if (bufferedDataRows.size() >= rowsToPrebuffer)
595         flushDataPages();
596 }
597 
flushObjectPages()598 void PdfExport::flushObjectPages()
599 {
600     if (bufferedObjectRows.isEmpty())
601         return;
602 
603     int y = getContentsTop();
604     int totalHeight = lastRowY - y;
605 
606     if (totalHeight > 0)
607     {
608         totalHeight += minRowHeight * 2; // a space between objects on one page
609         y += totalHeight;
610     }
611     else
612         newPage();
613 
614     while (!bufferedObjectRows.isEmpty())
615     {
616         ObjectRow& row = bufferedObjectRows.first();
617 
618         if (row.recalculateColumnWidths || row.cells.size() != calculatedObjectColumnWidths.size())
619             calculateObjectColumnWidths();
620 
621         totalHeight += row.height;
622         if (totalHeight > pageHeight)
623         {
624             newPage();
625             y = getContentsTop();
626             totalHeight = row.height;
627         }
628         flushObjectRow(row, y);
629 
630         y += row.height;
631 
632         bufferedObjectRows.removeFirst();
633     }
634 
635     lastRowY = getContentsTop() + totalHeight;
636 }
637 
drawObjectTopLine(int y)638 void PdfExport::drawObjectTopLine(int y)
639 {
640     painter->drawLine(getContentsLeft(), y, getContentsRight(), y);
641 }
642 
drawObjectCellHeaderBackground(int x1,int y1,int x2,int y2)643 void PdfExport::drawObjectCellHeaderBackground(int x1, int y1, int x2, int y2)
644 {
645     painter->save();
646     painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern));
647     painter->setPen(Qt::NoPen);
648     painter->drawRect(x1, y1, x2 - x1, y2 - y1);
649     painter->restore();
650 }
651 
drawFooter()652 void PdfExport::drawFooter()
653 {
654     QString footer = tr("Document generated with SQLiteStudio v%1").arg(SQLITESTUDIO->getVersionString());
655 
656     QTextOption opt = *textOption;
657     opt.setAlignment(Qt::AlignRight);
658 
659     int y = lastRowY + minRowHeight;
660     int height = pageHeight - y;
661     int txtHeight = painter->boundingRect(QRect(0, 0, pageWidth, height), footer, opt).height();
662     if ((y + txtHeight) > pageHeight)
663     {
664         newPage();
665         y = getContentsTop();
666     }
667 
668     painter->save();
669     painter->setFont(italicFont);
670     painter->drawText(QRect(getContentsLeft(), y, pageWidth, txtHeight), footer, opt);
671     painter->restore();
672 }
673 
flushObjectRow(const PdfExport::ObjectRow & row,int y)674 void PdfExport::flushObjectRow(const PdfExport::ObjectRow& row, int y)
675 {
676     painter->save();
677     int x = getContentsLeft();
678     int bottom = y + row.height;
679     int top = y;
680     int left = getContentsLeft();
681     int right = getContentsRight();
682     switch (row.type)
683     {
684         case ObjectRow::Type::SINGLE:
685         {
686             const ObjectCell& cell = row.cells.first();
687             if (cell.headerBackground)
688                 drawObjectCellHeaderBackground(left, y, right, bottom);
689 
690             painter->drawLine(left, y, left, bottom);
691             painter->drawLine(right, y, right, bottom);
692             painter->drawLine(left, top, right, top);
693             painter->drawLine(left, bottom, right, bottom);
694 
695             flushObjectCell(cell, left, y, pageWidth, row.height);
696             break;
697         }
698         case ObjectRow::Type::MULTI:
699         {
700             int width = 0;
701             for (int col = 0, total = calculatedObjectColumnWidths.size(); col < total; ++col)
702             {
703                 width = calculatedObjectColumnWidths[col];
704                 if (row.cells[col].headerBackground)
705                     drawObjectCellHeaderBackground(x, y, x + width, bottom);
706 
707                 x += width;
708             }
709 
710             x = left;
711             painter->drawLine(x, y, x, bottom);
712             for (int w : calculatedObjectColumnWidths)
713             {
714                 x += w;
715                 painter->drawLine(x, y, x, bottom);
716             }
717             painter->drawLine(left, top, right, top);
718             painter->drawLine(left, bottom, right, bottom);
719 
720             x = left;
721             for (int col = 0, total = calculatedObjectColumnWidths.size(); col < total; ++col)
722             {
723                 const ObjectCell& cell = row.cells[col];
724                 width = calculatedObjectColumnWidths[col];
725                 flushObjectCell(cell, x, y, width, row.height);
726                 x += width;
727             }
728             break;
729         }
730     }
731     painter->restore();
732 }
733 
flushObjectCell(const PdfExport::ObjectCell & cell,int x,int y,int w,int h)734 void PdfExport::flushObjectCell(const PdfExport::ObjectCell& cell, int x, int y, int w, int h)
735 {
736     QTextOption opt = *textOption;
737     opt.setAlignment(cell.alignment);
738 
739     if (cell.bold)
740         painter->setFont(boldFont);
741     else if (cell.italic)
742         painter->setFont(italicFont);
743 
744     switch (cell.type)
745     {
746         case ObjectCell::Type::NORMAL:
747         {
748             painter->drawText(QRect(x + padding, y + padding, w - 2 * padding, h - 2 * padding), cell.contents.first(), opt);
749             break;
750         }
751         case ObjectCell::Type::LIST:
752         {
753             static QString prefix = bulletChar + " ";
754             int prefixWidth = calculateBulletPrefixWidth();
755             x += padding;
756             y += padding;
757             w -= 2 * padding;
758             h -= 2 * padding;
759             int txtX = x + prefixWidth;
760             int txtW = w - prefixWidth;
761 
762             QTextOption prefixOpt = opt;
763             prefixOpt.setAlignment(opt.alignment() | Qt::AlignTop);
764 
765             int txtH = 0;
766             for (const QString& contents : cell.contents)
767             {
768                 txtH = calculateRowHeight(txtW, contents);
769                 painter->drawText(QRect(x, y, prefixWidth, txtH), prefix, prefixOpt);
770                 painter->drawText(QRect(txtX, y, txtW, txtH), contents, opt);
771                 y += txtH;
772             }
773             break;
774         }
775     }
776 }
777 
calculateObjectColumnWidths(int columnToExpand)778 void PdfExport::calculateObjectColumnWidths(int columnToExpand)
779 {
780     calculatedObjectColumnWidths.clear();
781     if (bufferedObjectRows.size() == 0)
782         return;
783 
784     QTextOption opt = *textOption;
785     opt.setWrapMode(QTextOption::NoWrap);
786 
787     int colCount = bufferedObjectRows.first().cells.size();
788     for (int i = 0; i < colCount; i++)
789         calculatedObjectColumnWidths << 0;
790 
791     int width = 0;
792     for (const ObjectRow& row : bufferedObjectRows)
793     {
794         if (row.cells.size() != colCount)
795             break;
796 
797         for (int col = 0; col < colCount; col++)
798         {
799             width = painter->boundingRect(QRectF(0, 0, 1, 1), row.cells[col].contents.join("\n"), opt).width();
800             width += 2 * padding;
801             calculatedObjectColumnWidths[col] = qMax(calculatedObjectColumnWidths[col], width);
802         }
803     }
804 
805     int totalWidth = correctMaxObjectColumnWidths(colCount, columnToExpand);
806     if (totalWidth < pageWidth)
807     {
808         int col = (columnToExpand > -1) ? columnToExpand : (colCount - 1);
809         calculatedObjectColumnWidths[col] += (pageWidth - totalWidth);
810     }
811 
812     calculateObjectRowHeights();
813 }
814 
correctMaxObjectColumnWidths(int colCount,int columnToExpand)815 int PdfExport::correctMaxObjectColumnWidths(int colCount, int columnToExpand)
816 {
817     int totalWidth = 0;
818     for (int w : calculatedObjectColumnWidths)
819         totalWidth += w;
820 
821     int maxWidth = pageWidth / colCount;
822     if (totalWidth <= pageWidth)
823         return totalWidth;
824 
825     int tmpWidth = 0;
826     for (int col = 0; col < colCount && totalWidth > pageWidth; col++)
827     {
828         if (calculatedObjectColumnWidths[col] <= maxWidth)
829             continue;
830 
831         if (col == columnToExpand)
832             continue; // will handle that column as last one (if needed)
833 
834         tmpWidth = calculatedObjectColumnWidths[col];
835         if ((totalWidth - calculatedObjectColumnWidths[col] + maxWidth) <= pageWidth)
836         {
837             calculatedObjectColumnWidths[col] -= (pageWidth - totalWidth + calculatedObjectColumnWidths[col] - maxWidth);
838             return pageWidth; // the 'if' condition guarantees that shrinking this column that much will give us pageWidth
839         }
840         else
841             calculatedObjectColumnWidths[col] = maxWidth;
842 
843         totalWidth -= tmpWidth - calculatedObjectColumnWidths[col];
844     }
845 
846     if (columnToExpand > -1 && totalWidth > pageWidth)
847     {
848         tmpWidth = calculatedObjectColumnWidths[columnToExpand];
849         if ((totalWidth - calculatedObjectColumnWidths[columnToExpand] + maxWidth) <= pageWidth)
850             calculatedObjectColumnWidths[columnToExpand] -= (pageWidth - totalWidth + calculatedObjectColumnWidths[columnToExpand] - maxWidth);
851         else
852             calculatedObjectColumnWidths[columnToExpand] = maxWidth;
853     }
854 
855     return pageWidth;
856 }
857 
calculateObjectRowHeights()858 void PdfExport::calculateObjectRowHeights()
859 {
860     int maxHeight = 0;
861     int colWidth = 0;
862     int height = 0;
863     int colCount = calculatedObjectColumnWidths.size();
864     for (ObjectRow& row : bufferedObjectRows)
865     {
866         if (row.cells.size() != colCount)
867             return; // stop at this row, further calculation will be done when columns are recalculated
868 
869         maxHeight = 0;
870         for (int col = 0; col < colCount; ++col)
871         {
872             colWidth = calculatedObjectColumnWidths[col];
873             const ObjectCell& cell = row.cells[col];
874 
875             switch (cell.type)
876             {
877                 case ObjectCell::Type::NORMAL:
878                     height = calculateRowHeight(colWidth, cell.contents.first());
879                     break;
880                 case ObjectCell::Type::LIST:
881                     height = calculateRowHeight(colWidth, cell.contents);
882                     break;
883             }
884             maxHeight = qMax(maxHeight, height);
885         }
886 
887         row.height = qMin(maxRowHeight, maxHeight);
888     }
889 }
890 
flushDataPages(bool forceRender)891 void PdfExport::flushDataPages(bool forceRender)
892 {
893     calculateDataRowHeights();
894 
895     int rowsToRender = 0;
896     int totalRowHeight = 0;
897     int colStartAt = 0;
898     while ((bufferedDataRows.size() >= rowsToPrebuffer) || (forceRender && bufferedDataRows.size() > 0))
899     {
900         // Calculate how many rows we can render on single page
901         rowsToRender = 0;
902         totalRowHeight = totalHeaderRowsHeight;
903         for (const DataRow& row : bufferedDataRows)
904         {
905             rowsToRender++;
906             totalRowHeight += row.height;
907             if (totalRowHeight >= pageHeight)
908             {
909                 rowsToRender--;
910                 break;
911             }
912         }
913 
914         // Render limited number of columns and rows per single page
915         colStartAt = 0;
916         for (int cols : columnsPerPage)
917         {
918             newPage();
919             flushDataRowsPage(colStartAt, colStartAt + cols, rowsToRender);
920             colStartAt += cols;
921         }
922 
923         for (int i = 0; i < rowsToRender; i++)
924             bufferedDataRows.removeFirst();
925 
926         rowNum += rowsToRender;
927     }
928 }
929 
flushDataRowsPage(int columnStart,int columnEndBefore,int rowsToRender)930 void PdfExport::flushDataRowsPage(int columnStart, int columnEndBefore, int rowsToRender)
931 {
932     QList<DataRow> allRows;
933     if (headerRow)
934         allRows += *headerRow;
935 
936     if (columnsHeaderRow)
937         allRows += *columnsHeaderRow;
938 
939     allRows += bufferedDataRows.mid(0, rowsToRender);
940 
941     int left = getContentsLeft();
942     int right = getContentsRight();
943     int top = getContentsTop();
944 
945     // Calculating width of all columns on this page
946     int totalColumnsWidth = sum(calculatedDataColumnWidths.mid(columnStart, columnEndBefore - columnStart));
947     int totalColumnsWidthWithRowId = totalColumnsWidth + rowNumColumnWidth;
948 
949     // Calculating height of all rows
950     int totalRowsHeight = 0;
951     for (const DataRow& row : allRows)
952         totalRowsHeight += row.height;
953 
954     // Draw header background
955     int x = getDataColumnsStartX();
956     painter->save();
957     painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern));
958     painter->setPen(Qt::NoPen);
959     painter->drawRect(QRect(x, top, totalColumnsWidth, totalHeaderRowsHeight));
960     painter->restore();
961 
962     // Draw rowNum background
963     if (printRowNum)
964     {
965         painter->save();
966         painter->setBrush(QBrush(cfg.PdfExport.HeaderBgColor.get(), Qt::SolidPattern));
967         painter->setPen(Qt::NoPen);
968         painter->drawRect(QRect(left, top, rowNumColumnWidth, totalRowsHeight));
969         painter->restore();
970     }
971 
972     // Draw horizontal lines
973     int y = top;
974     int horizontalLineEnd = x + totalColumnsWidth;
975     painter->drawLine(left, y, horizontalLineEnd, y);
976     for (const DataRow& row : allRows)
977     {
978         y += row.height;
979         painter->drawLine(left, y, horizontalLineEnd, y);
980     }
981 
982     // Draw dashed horizontal lines if there are more columns on the next page and there is space on the right side
983     if (columnEndBefore < calculatedDataColumnWidths.size() && horizontalLineEnd < right)
984     {
985         y = top;
986         painter->save();
987         QPen pen(Qt::lightGray, lineWidth, Qt::DashLine);
988         pen.setDashPattern(QVector<qreal>({5.0, 3.0}));
989         painter->setPen(pen);
990         painter->drawLine(horizontalLineEnd, y, right, y);
991         for (const DataRow& row : allRows)
992         {
993             y += row.height;
994             painter->drawLine(horizontalLineEnd, y, right, y);
995         }
996         painter->restore();
997     }
998 
999     // Finding first row to start vertical lines from. It's either a COLUMNS_HEADER, or first data row, after headers.
1000     int verticalLinesStart = top;
1001     if (headerRow)
1002         verticalLinesStart += headerRow->height;
1003 
1004     // Draw vertical lines
1005     x = getDataColumnsStartX();
1006     painter->drawLine(left, top, left, top + totalRowsHeight);
1007     if (printRowNum)
1008         painter->drawLine(x, verticalLinesStart, x, top + totalRowsHeight);
1009 
1010     for (int col = columnStart; col < columnEndBefore; col++)
1011     {
1012         x += calculatedDataColumnWidths[col];
1013         painter->drawLine(x, (col+1 == columnEndBefore) ? top : verticalLinesStart, x, top + totalRowsHeight);
1014     }
1015 
1016     // Draw header rows
1017     y = top;
1018     if (headerRow)
1019         flushDataHeaderRow(*headerRow, y, totalColumnsWidthWithRowId, columnStart, columnEndBefore);
1020 
1021     if (columnsHeaderRow)
1022         flushDataHeaderRow(*columnsHeaderRow, y, totalColumnsWidthWithRowId, columnStart, columnEndBefore);
1023 
1024     // Draw data
1025     int localRowNum = rowNum;
1026     for (int rowCounter = 0; rowCounter < rowsToRender && !bufferedDataRows.isEmpty(); rowCounter++)
1027         flushDataRow(bufferedDataRows[rowCounter], y, columnStart, columnEndBefore, localRowNum++);
1028 
1029     lastRowY = y;
1030 }
1031 
flushDataRow(const DataRow & row,int & y,int columnStart,int columnEndBefore,int localRowNum)1032 void PdfExport::flushDataRow(const DataRow& row, int& y, int columnStart, int columnEndBefore, int localRowNum)
1033 {
1034     int textWidth = 0;
1035     int textHeight = 0;
1036     int colWidth = 0;
1037     int x = getContentsLeft();
1038 
1039     y += padding;
1040     if (printRowNum)
1041     {
1042         QTextOption opt = *textOption;
1043         opt.setAlignment(Qt::AlignRight);
1044 
1045         x += padding;
1046         textWidth = rowNumColumnWidth - padding * 2;
1047         textHeight = row.height - padding * 2;
1048         flushDataCell(QRect(x, y, textWidth, textHeight), QString::number(localRowNum), &opt);
1049         x += rowNumColumnWidth - padding;
1050     }
1051 
1052     for (int col = columnStart; col < columnEndBefore; col++)
1053     {
1054         const DataCell& cell = row.cells[col];
1055         colWidth = calculatedDataColumnWidths[col];
1056 
1057         x += padding;
1058         textWidth = colWidth - padding * 2;
1059         textHeight = row.height - padding * 2;
1060         flushDataCell(QRect(x, y, textWidth, textHeight), cell);
1061         x += colWidth - padding;
1062     }
1063     y += row.height - padding;
1064 }
1065 
flushDataCell(const QRect & rect,const PdfExport::DataCell & cell)1066 void PdfExport::flushDataCell(const QRect& rect, const PdfExport::DataCell& cell)
1067 {
1068     QTextOption opt = *textOption;
1069     opt.setAlignment(cell.alignment);
1070 
1071     painter->save();
1072     if (cell.isNull)
1073     {
1074         painter->setPen(cfg.PdfExport.NullValueColor.get());
1075         painter->setFont(italicFont);
1076     }
1077 
1078     painter->drawText(rect, cell.contents.left(cellDataLimit), opt);
1079     painter->restore();
1080 }
1081 
flushDataCell(const QRect & rect,const QString & contents,QTextOption * opt)1082 void PdfExport::flushDataCell(const QRect& rect, const QString& contents, QTextOption* opt)
1083 {
1084     painter->drawText(rect, contents.left(cellDataLimit), *opt);
1085 }
1086 
flushDataHeaderRow(const PdfExport::DataRow & row,int & y,int totalColsWidth,int columnStart,int columnEndBefore)1087 void PdfExport::flushDataHeaderRow(const PdfExport::DataRow& row, int& y, int totalColsWidth, int columnStart, int columnEndBefore)
1088 {
1089     QTextOption opt = *textOption;
1090     opt.setAlignment(Qt::AlignHCenter);
1091     int x = getContentsLeft();
1092     y += padding;
1093     switch (row.type)
1094     {
1095         case DataRow::Type::TOP_HEADER:
1096         {
1097             x += padding;
1098             painter->save();
1099             painter->setFont(boldFont);
1100             painter->drawText(QRect(x, y, totalColsWidth - 2 * padding, row.height - 2 * padding), row.cells.first().contents, opt);
1101             painter->restore();
1102             break;
1103         }
1104         case DataRow::Type::COLUMNS_HEADER:
1105         {
1106             if (printRowNum)
1107             {
1108                 x += padding;
1109                 int textWidth = rowNumColumnWidth - padding * 2;
1110                 int textHeight = row.height - padding * 2;
1111                 painter->drawText(QRect(x, y, textWidth, textHeight), "#", opt);
1112                 x += rowNumColumnWidth - padding;
1113             }
1114 
1115             for (int col = columnStart; col < columnEndBefore; col++)
1116                 flushDataHeaderCell(x, y, row, col, &opt);
1117 
1118             break;
1119         }
1120         case DataRow::Type::NORMAL:
1121             break; // no-op
1122     }
1123     y += row.height - padding;
1124 }
1125 
flushDataHeaderCell(int & x,int y,const PdfExport::DataRow & row,int col,QTextOption * opt)1126 void PdfExport::flushDataHeaderCell(int& x, int y, const PdfExport::DataRow& row, int col, QTextOption* opt)
1127 {
1128     x += padding;
1129     painter->drawText(QRect(x, y, calculatedDataColumnWidths[col] - 2 * padding, row.height - 2 * padding), row.cells[col].contents, *opt);
1130     x += calculatedDataColumnWidths[col] - padding;
1131 }
1132 
renderPageNumber()1133 void PdfExport::renderPageNumber()
1134 {
1135     if (!printPageNumbers)
1136         return;
1137 
1138     QString page = QString::number(currentPage + 1);
1139 
1140     QTextOption opt = *textOption;
1141     opt.setWrapMode(QTextOption::NoWrap);
1142 
1143     painter->save();
1144     painter->setFont(italicFont);
1145     QRect rect = painter->boundingRect(QRect(0, 0, 1, 1), page, opt).toRect();
1146     int x = getContentsRight() - rect.width();
1147     int y = getContentsBottom(); // the bottom margin was already increased to hold page numbers
1148     QRect newRect(x, y, rect.width(), rect.height());
1149     painter->drawText(newRect, page, *textOption);
1150     painter->restore();
1151 }
1152 
getPageNumberHeight()1153 int PdfExport::getPageNumberHeight()
1154 {
1155     QTextOption opt = *textOption;
1156     opt.setWrapMode(QTextOption::NoWrap);
1157 
1158     painter->save();
1159     painter->setFont(italicFont);
1160     int height = painter->boundingRect(QRect(0, 0, 1, 1), "0123456789", opt).height();
1161     painter->restore();
1162     return height;
1163 }
1164 
exportDataHeader(const QString & contents)1165 void PdfExport::exportDataHeader(const QString& contents)
1166 {
1167     DataRow* row = new DataRow;
1168     row->type = DataRow::Type::TOP_HEADER;
1169 
1170     DataCell cell;
1171     cell.contents = contents;
1172     cell.alignment = Qt::AlignHCenter;
1173     row->cells << cell;
1174 
1175     headerRow.reset(row);
1176 }
1177 
exportDataColumnsHeader(const QStringList & columns)1178 void PdfExport::exportDataColumnsHeader(const QStringList& columns)
1179 {
1180     DataRow* row = new DataRow;
1181     row->type = DataRow::Type::COLUMNS_HEADER;
1182 
1183     DataCell cell;
1184     cell.alignment = Qt::AlignHCenter;
1185     for (const QString& col : columns)
1186     {
1187         cell.contents = col;
1188         row->cells << cell;
1189     }
1190 
1191     columnsHeaderRow.reset(row);
1192 }
1193 
newPage()1194 void PdfExport::newPage()
1195 {
1196     if (currentPage < 0)
1197     {
1198         currentPage = 0;
1199         renderPageNumber();
1200         return;
1201     }
1202 
1203     pagedWriter->newPage();
1204     currentPage++;
1205     lastRowY = getContentsTop();
1206     renderPageNumber();
1207 }
1208 
calculateDataColumnWidths(const QStringList & columnNames,const QList<int> & columnDataLengths,int columnToExpand)1209 void PdfExport::calculateDataColumnWidths(const QStringList& columnNames, const QList<int>& columnDataLengths, int columnToExpand)
1210 {
1211     static const QString tplChar = QStringLiteral("W");
1212 
1213     // Text options for calculating widths will not allow word wrapping
1214     QTextOption opt = *textOption;
1215     opt.setWrapMode(QTextOption::NoWrap);
1216 
1217     // Calculate header width first
1218     if (columnToExpand > -1)
1219     {
1220         // If any column was picked for expanding table to page width, the header will also be full page width.
1221         // This will also result later with expanding selected column to the header width = page width.
1222         currentHeaderMinWidth = pageWidth;
1223     }
1224     else
1225     {
1226         currentHeaderMinWidth = 0;
1227         if (headerRow)
1228         {
1229             painter->save();
1230             painter->setFont(boldFont);
1231             currentHeaderMinWidth = painter->boundingRect(QRectF(0, 0, 1, 1), headerRow->cells.first().contents, opt).width();
1232             currentHeaderMinWidth += padding * 2;
1233             painter->restore();
1234         }
1235     }
1236 
1237     // Calculate width of rowNum column (if enabled)
1238     rowNumColumnWidth = 0;
1239     if (printRowNum)
1240         rowNumColumnWidth = painter->boundingRect(QRectF(0, 0, 1, 1), QString::number(totalRows), opt).width() + 2 * padding;
1241 
1242     // Precalculate column widths for the header row
1243     QList<int> headerWidths;
1244     for (const QString& colName : columnNames)
1245         headerWidths << painter->boundingRect(QRectF(0, 0, 1, 1), colName, opt).width();
1246 
1247     // Calculate width for each column and compare it with its header width, then pick the wider, but never wider than the maximum width.
1248     calculatedDataColumnWidths.clear();
1249     int dataWidth = 0;
1250     int headerWidth = 0;
1251     int totalWidth = 0;
1252     for (int i = 0, total = columnDataLengths.size(); i < total; ++i)
1253     {
1254         dataWidth = painter->boundingRect(QRectF(0, 0, 1, 1), tplChar.repeated(columnDataLengths[i]), opt).width();
1255         headerWidth = headerWidths[i];
1256 
1257         // Pick the wider one, but never wider than maxColWidth
1258         totalWidth = qMax(dataWidth, headerWidth) + padding * 2; // wider one + padding on sides
1259         calculatedDataColumnWidths << qMin(maxColWidth, totalWidth);
1260     }
1261 
1262     // Calculate how many columns will fit for every page, until the full row is rendered.
1263     columnsPerPage.clear();
1264     int colsForThePage = 0;
1265     int currentTotalWidth = 0;
1266     int expandColumnIndex = 0;
1267     int dataColumnsWidth = getDataColumnsWidth();
1268     for (int i = 0, total = calculatedDataColumnWidths.size(); i < total; ++i)
1269     {
1270         colsForThePage++;
1271         currentTotalWidth += calculatedDataColumnWidths[i];
1272         if (currentTotalWidth > dataColumnsWidth)
1273         {
1274             colsForThePage--;
1275             columnsPerPage << colsForThePage;
1276 
1277             // Make sure that columns on previous page are at least as wide as the header
1278             currentTotalWidth -= calculatedDataColumnWidths[i];
1279             if ((currentTotalWidth + rowNumColumnWidth) < currentHeaderMinWidth && i > 0)
1280             {
1281                 expandColumnIndex = 1;
1282                 if (columnToExpand > -1)
1283                     expandColumnIndex = colsForThePage - columnToExpand;
1284 
1285                 calculatedDataColumnWidths[i - expandColumnIndex] += (currentHeaderMinWidth - (currentTotalWidth + rowNumColumnWidth));
1286             }
1287 
1288             // Reset values fot next interation
1289             currentTotalWidth = calculatedDataColumnWidths[i];
1290             colsForThePage = 1;
1291         }
1292     }
1293 
1294     if (colsForThePage > 0)
1295     {
1296         columnsPerPage << colsForThePage;
1297         if ((currentTotalWidth + rowNumColumnWidth) < currentHeaderMinWidth && !calculatedDataColumnWidths.isEmpty())
1298         {
1299             int i = calculatedDataColumnWidths.size();
1300             expandColumnIndex = 1;
1301             if (columnToExpand > -1)
1302                 expandColumnIndex = colsForThePage - columnToExpand;
1303 
1304             calculatedDataColumnWidths[i - expandColumnIndex] += (currentHeaderMinWidth - (currentTotalWidth + rowNumColumnWidth));
1305         }
1306     }
1307 }
1308 
calculateDataRowHeights()1309 void PdfExport::calculateDataRowHeights()
1310 {
1311     // Calculating heights for data rows
1312     int thisRowMaxHeight = 0;
1313     int actualColHeight = 0;
1314     for (DataRow& row : bufferedDataRows)
1315     {
1316         if (row.height > 0) // was calculated in the previous rendering iteration
1317             continue;
1318 
1319         thisRowMaxHeight = 0;
1320         for (int col = 0, total = row.cells.size(); col < total; ++col)
1321         {
1322             // We pass rect, that is as wide as calculated column width and we look how height extends
1323             actualColHeight = calculateRowHeight(calculatedDataColumnWidths[col], row.cells[col].contents);
1324             thisRowMaxHeight = qMax(thisRowMaxHeight, actualColHeight);
1325         }
1326         row.height = qMin(maxRowHeight, thisRowMaxHeight);
1327     }
1328 
1329     // Calculating heights for header rows
1330     totalHeaderRowsHeight = 0;
1331     if (headerRow)
1332     {
1333         painter->save();
1334         painter->setFont(boldFont);
1335         // Main header can be as wide as page, so that's the rect we pass
1336         actualColHeight = calculateRowHeight(pageWidth, headerRow->cells.first().contents);
1337 
1338         headerRow->height = qMin(maxRowHeight, actualColHeight);
1339         totalHeaderRowsHeight += headerRow->height;
1340         painter->restore();
1341     }
1342 
1343     if (columnsHeaderRow)
1344     {
1345         thisRowMaxHeight = 0;
1346         for (int col = 0, total = columnsHeaderRow->cells.size(); col < total; ++col)
1347         {
1348             // This is the same as for data rows (see above)
1349             actualColHeight = calculateRowHeight(calculatedDataColumnWidths[col], columnsHeaderRow->cells[col].contents);
1350             thisRowMaxHeight = qMax(thisRowMaxHeight, actualColHeight);
1351         }
1352 
1353         columnsHeaderRow->height = qMin(maxRowHeight, thisRowMaxHeight);
1354         totalHeaderRowsHeight += columnsHeaderRow->height;
1355     }
1356 
1357 }
1358 
calculateRowHeight(int maxTextWidth,const QString & contents)1359 int PdfExport::calculateRowHeight(int maxTextWidth, const QString& contents)
1360 {
1361     // Measures height expanding due to constrained text width, line wrapping and top+bottom padding
1362     return painter->boundingRect(QRect(0, 0, (maxTextWidth - padding * 2), 1), contents, *textOption).height() + padding * 2;
1363 }
1364 
getDataColumnsWidth() const1365 int PdfExport::getDataColumnsWidth() const
1366 {
1367     if (printRowNum)
1368         return pageWidth - rowNumColumnWidth;
1369 
1370     return pageWidth;
1371 }
1372 
getDataColumnsStartX() const1373 int PdfExport::getDataColumnsStartX() const
1374 {
1375     if (printRowNum)
1376         return getContentsLeft() + rowNumColumnWidth;
1377 
1378     return getContentsLeft();
1379 }
1380 
getContentsLeft() const1381 int PdfExport::getContentsLeft() const
1382 {
1383     return leftMargin;
1384 }
1385 
getContentsTop() const1386 int PdfExport::getContentsTop() const
1387 {
1388     return topMargin;
1389 }
1390 
getContentsRight() const1391 int PdfExport::getContentsRight() const
1392 {
1393     return getContentsLeft() + pageWidth;
1394 }
1395 
getContentsBottom() const1396 int PdfExport::getContentsBottom() const
1397 {
1398     return topMargin + pageHeight;
1399 }
1400 
mmToPoints(qreal sizeMM)1401 qreal PdfExport::mmToPoints(qreal sizeMM)
1402 {
1403     return pointsPerMm * sizeMM;
1404 }
1405 
getConfig()1406 CfgMain* PdfExport::getConfig()
1407 {
1408     return &cfg;
1409 }
1410 
getExportConfigFormName() const1411 QString PdfExport::getExportConfigFormName() const
1412 {
1413     return "PdfExportConfig";
1414 }
1415 
getPdfExportDefaultFont()1416 QFont Cfg::getPdfExportDefaultFont()
1417 {
1418     QPainter p;
1419     return p.font();
1420 }
1421 
getPdfPageSizes()1422 QStringList Cfg::getPdfPageSizes()
1423 {
1424     return getAllPageSizes();
1425 }
1426