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